Migrated server logic. Added all server endpoints. Migrated some more stuff

This commit is contained in:
Marcel Baumgartner 2023-02-20 21:12:10 +01:00
parent 95999eae26
commit c3eadf9133
82 changed files with 5553 additions and 186 deletions

View file

@ -8,7 +8,7 @@ using Moonlight.App.Database;
#nullable disable #nullable disable
namespace Moonlight.App.DatabaseMigrations namespace Moonlight.App.Database.Migrations
{ {
[DbContext(typeof(DataContext))] [DbContext(typeof(DataContext))]
[Migration("20230217152643_MigratedSomeModels")] [Migration("20230217152643_MigratedSomeModels")]

View file

@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable #nullable disable
namespace Moonlight.App.DatabaseMigrations namespace Moonlight.App.Database.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public partial class MigratedSomeModels : Migration public partial class MigratedSomeModels : Migration

View 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)
{
}
}

View 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";
}
}
}

View 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);
}
}

View 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;
}
}

View 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);
}

View 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}");
}
}
}
}

View 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;
}
}
}

View 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();
}
}

View 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;
}
}

View file

@ -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();
}
}

View 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();
}
}

View file

@ -0,0 +1,7 @@
namespace Moonlight.App.Http.Requests.Wings;
public class ReportBackupCompleteRequest
{
public bool Successful { get; set; }
public long Size { get; set; }
}

View 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; }
}

View 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; }
}
}

View 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; }
}

View 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();
}
}

View 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; }
}

View 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;
}
}

View 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; }
}

View 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}");
}
}

View 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; }
}

View 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();
}

View 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; }
}

View 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; }
}

View file

@ -0,0 +1,9 @@
namespace Moonlight.App.Models.Wings;
public enum PowerSignal
{
Start,
Stop,
Kill,
Restart
}

View 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; }
}

View file

@ -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; }
}

View 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; }
}

View 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();
}

View 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; }
}

View 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; } = "";
}

View file

@ -0,0 +1,9 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Wings.Requests;
public class ServerPowerRequest
{
[JsonProperty("action")]
public string Action { get; set; }
}

View 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; }
}

View file

@ -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; }
}
}

View 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; }
}

View 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();
}
}

View file

@ -0,0 +1,11 @@
using Moonlight.App.MessageSystem;
namespace Moonlight.App.Services;
public class MessageService : MessageSender
{
public MessageService()
{
Debug = true;
}
}

View file

@ -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; namespace Moonlight.App.Services;
public class NodeService public class NodeService
{ {
private readonly NodeRepository NodeRepository; private readonly NodeRepository NodeRepository;
private readonly WingsApiHelper WingsApiHelper;
public NodeService(NodeRepository nodeRepository) public NodeService(NodeRepository nodeRepository, WingsApiHelper wingsApiHelper)
{ {
NodeRepository = nodeRepository; NodeRepository = nodeRepository;
WingsApiHelper = wingsApiHelper;
}
public async Task<SystemStatus> GetStatus(Node node)
{
return await WingsApiHelper.Get<SystemStatus>(node, "api/system");
} }
} }

View 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();
}
}

View 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");
}
}
}

View file

@ -13,7 +13,8 @@
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Ben.Demystifier" Version="0.4.1" /> <PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="Blazor.ContextMenu" Version="1.15.0" /> <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="BlazorTable" Version="1.17.0" />
<PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.4.0" /> <PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.4.0" />
<PackageReference Include="Discord.Net" Version="3.9.0" /> <PackageReference Include="Discord.Net" Version="3.9.0" />
@ -56,10 +57,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="App\Database\Migrations" />
<Folder Include="App\Http\Middleware" /> <Folder Include="App\Http\Middleware" />
<Folder Include="App\Http\Requests" />
<Folder Include="App\Http\Resources" />
<Folder Include="App\Models\AuditLogData" /> <Folder Include="App\Models\AuditLogData" />
<Folder Include="resources\lang" /> <Folder Include="resources\lang" />
<Folder Include="wwwroot\assets\media" /> <Folder Include="wwwroot\assets\media" />

View file

@ -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/style.bundle.css"/>
<link rel="stylesheet" type="text/css" href="/assets/css/flashbang.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/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/boxicons.min.css"/>
<link rel="stylesheet" type="text/css" href="/assets/css/blazor.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/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/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/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"/> <link href="/assets/plugins/global/plugins.bundle.css" rel="stylesheet" type="text/css"/>
@ -80,22 +81,26 @@
</div> </div>
<script src="/_framework/blazor.server.js"></script> <script src="/_framework/blazor.server.js"></script>
<script src="/assets/plugins/global/plugins.bundle.js"></script> <script src="/assets/plugins/global/plugins.bundle.js"></script>
<script src="/_content/XtermBlazor/XtermBlazor.min.js"></script> <script src="/_content/XtermBlazor/XtermBlazor.min.js"></script>
<script src="/_content/BlazorTable/BlazorTable.min.js"></script> <script src="/_content/BlazorTable/BlazorTable.min.js"></script>
<script src="/_content/BlazorInputFile/inputfile.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/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
<script src="/_content/Blazor.ContextMenu/blazorContextMenu.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://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="http://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="http://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-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/scripts.bundle.js"></script>
<script src="/assets/js/flashbang.js"></script> <script src="/assets/js/flashbang.js"></script>
@ -107,6 +112,5 @@
<script src="/assets/js/loggingUtils.js"></script> <script src="/assets/js/loggingUtils.js"></script>
<script src="/assets/js/snow.js"></script> <script src="/assets/js/snow.js"></script>
<script src="/assets/js/recaptcha.js"></script> <script src="/assets/js/recaptcha.js"></script>
<script src="/assets/js/xtermAddons.js"></script>
</body> </body>
</html> </html>

View file

@ -35,6 +35,7 @@ namespace Moonlight
builder.Services.AddScoped<ServerBackupRepository>(); builder.Services.AddScoped<ServerBackupRepository>();
builder.Services.AddScoped<AuditLogRepository>(); builder.Services.AddScoped<AuditLogRepository>();
builder.Services.AddScoped<DatabaseRepository>(); builder.Services.AddScoped<DatabaseRepository>();
builder.Services.AddScoped<ImageRepository>();
// Services // Services
builder.Services.AddSingleton<ConfigService>(); builder.Services.AddSingleton<ConfigService>();
@ -47,13 +48,23 @@ namespace Moonlight
builder.Services.AddScoped<UserService>(); builder.Services.AddScoped<UserService>();
builder.Services.AddScoped<TotpService>(); builder.Services.AddScoped<TotpService>();
builder.Services.AddScoped<ToastService>(); 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<AuditLogService>();
builder.Services.AddScoped<SystemAuditLogService>(); builder.Services.AddScoped<SystemAuditLogService>();
// Helpers // Helpers
builder.Services.AddSingleton<SmartTranslateHelper>(); 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 // Third party services
builder.Services.AddBlazorTable(); builder.Services.AddBlazorTable();

View file

@ -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());
}
}

View file

@ -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"));
}
}

View 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);
}
}

View file

@ -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);
}
}
}
}

View 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);
}
}

View 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
}

View file

@ -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; }
}

View file

@ -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>

View file

@ -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; }
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View 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)
{
}
}
}

View file

@ -2,13 +2,15 @@
@using Moonlight.App.Repositories @using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities @using Moonlight.App.Database.Entities
@using Moonlight.App.Services @using Moonlight.App.Services
@using Microsoft.EntityFrameworkCore
@using BlazorTable
@inject NodeRepository NodeRepository @inject NodeRepository NodeRepository
@inject SmartTranslateService SmartTranslateService @inject SmartTranslateService SmartTranslateService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<OnlyAdmin> <OnlyAdmin>
<LazyLoader Load="Load"> <LazyLoader Load="Load" @ref="LazyLoader">
@if (Node == null) @if (Node == null)
{ {
<div class="alert alert-warning"> <div class="alert alert-warning">
@ -17,75 +19,112 @@
} }
else else
{ {
<div class="d-flex flex-center"> <div class="d-flex">
<div class="card rounded-3 w-md-550px"> <div class="flex-column">
<div class="card-body"> <div class="card rounded-3 w-md-550px">
<div class="d-flex flex-center flex-column-fluid"> <div class="card-body">
<div class="form w-100 fv-plugins-bootstrap5 fv-plugins-framework"> <div class="d-flex flex-center flex-column-fluid">
<div class="fv-row mb-8"> <div class="form w-100 fv-plugins-bootstrap5 fv-plugins-framework">
<label> <div class="fv-row mb-8">
<TL>Nodename</TL> <label class="form-label">
</label> <TL>Nodename</TL>
<input @bind="Node.Name" type="text" placeholder="@(SmartTranslateService.Translate("Nodename"))" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<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>
<TL>Token Id</TL>
</label>
<input @bind="Node.TokenId" type="text" placeholder="@(SmartTranslateService.Translate("Toekn Id"))" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<label>
<TL>Token</TL>
</label>
<input @bind="Node.Token" type="text" placeholder="@(SmartTranslateService.Translate("Token"))" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<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>
<TL>Sftp port</TL>
</label>
<input @bind="Node.SftpPort" type="number" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<label>
<TL>Moonlight daemon port</TL>
</label>
<input @bind="Node.MoonlightDaemonPort" type="number" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<div class="input-group">
<label class="col-lg-4 col-form-label fw-semibold fs-6">
<TL>SSL</TL>
</label> </label>
<div class="col-lg-8 d-flex align-items-center"> <input @bind="Node.Name" type="text" placeholder="@(SmartTranslateService.Translate("Nodename"))" class="form-control bg-transparent">
<div class="form-check form-check-solid form-switch form-check-custom fv-row"> </div>
<input @bind="Node.Ssl" class="form-check-input w-45px h-30px" type="checkbox" id="ssl"> <div class="fv-row mb-8">
<label class="form-check-label" for="ssl"></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 class="form-label">
<TL>Token Id</TL>
</label>
<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 class="form-label">
<TL>Token</TL>
</label>
<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 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 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 class="form-label">
<TL>Moonlight daemon port</TL>
</label>
<input @bind="Node.MoonlightDaemonPort" type="number" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<div class="input-group">
<label class="col-lg-4 col-form-label fw-semibold fs-6">
<TL>SSL</TL>
</label>
<div class="col-lg-8 d-flex align-items-center">
<div class="form-check form-check-solid form-switch form-check-custom fv-row">
<input @bind="Node.Ssl" class="form-check-input w-45px h-30px" type="checkbox" id="ssl">
<label class="form-check-label" for="ssl"></label>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="fv-row mb-9">
<WButton Text="@(SmartTranslateService.Translate("Save"))"
WorkingText="@(SmartTranslateService.Translate("Saving"))"
CssClasses="btn-success"
OnClick="Save">
</WButton>
<a href="/admin/nodes" class="btn btn-primary">
<TL>Back</TL>
</a>
</div>
</div> </div>
<div class="fv-row mb-9"> </div>
<WButton Text="@(SmartTranslateService.Translate("Save"))" </div>
WorkingText="@(SmartTranslateService.Translate("Saving"))" </div>
CssClasses="btn-primary" </div>
OnClick="Save"> <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> </WButton>
</div> </div>
</div> </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> </div>
</div> </div>
@ -99,17 +138,49 @@
public int Id { get; set; } public int Id { get; set; }
private Node? Node; 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() private Task Save()
{ {
NodeRepository.Update(Node); NodeRepository.Update(Node!);
NavigationManager.NavigateTo("/admin/nodes"); NavigationManager.NavigateTo("/admin/nodes");
return Task.CompletedTask; 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();
}
} }

View file

@ -3,23 +3,32 @@
@using Moonlight.App.Database.Entities @using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers @using Moonlight.App.Helpers
@using Moonlight.App.Models.Node @using Moonlight.App.Models.Node
@using Moonlight.App.Models.Wings.Resources
@using Moonlight.App.Services @using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Logging.Net
@inject NodeRepository NodeRepository @inject NodeRepository NodeRepository
@inject AlertService AlertService
@inject NodeService NodeService
@inject SmartTranslateService SmartTranslateService @inject SmartTranslateService SmartTranslateService
<OnlyAdmin> <OnlyAdmin>
<div class="row mb-5"> <div class="row mb-5">
<div class="card card-body"> <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> </div>
<div class="row"> <div class="row">
<LazyLoader Load="Load"> <LazyLoader @ref="LazyLoader" Load="Load">
@if (!Nodes.Any()) @if (!Nodes.Any())
{ {
<div class="alert alert-info"> <div class="card card-body">
<TL>No nodes found. Start with adding a new node</TL> <div class="alert alert-info">
<TL>No nodes found. Start with adding a new node</TL>
</div>
</div> </div>
} }
else else
@ -34,6 +43,32 @@
</h4> </h4>
</div> </div>
<div class="card-body pt-6"> <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 flex-stack">
<div class="d-flex align-items-center me-3"> <div class="d-flex align-items-center me-3">
<div class="flex-grow-1"> <div class="flex-grow-1">
@ -50,7 +85,7 @@
@{ @{
var cpu = CpuCache.ContainsKey(node) ? CpuCache[node] : null; var cpu = CpuCache.ContainsKey(node) ? CpuCache[node] : null;
} }
@if (cpu == null) @if (cpu == null)
{ {
<span>Loading</span> <span>Loading</span>
@ -79,7 +114,7 @@
@{ @{
var memory = MemoryCache.ContainsKey(node) ? MemoryCache[node] : null; var memory = MemoryCache.ContainsKey(node) ? MemoryCache[node] : null;
} }
@if (memory == null) @if (memory == null)
{ {
<span>Loading</span> <span>Loading</span>
@ -108,7 +143,7 @@
@{ @{
var disk = DiskCache.ContainsKey(node) ? DiskCache[node] : null; var disk = DiskCache.ContainsKey(node) ? DiskCache[node] : null;
} }
@if (disk == null) @if (disk == null)
{ {
<span>Loading</span> <span>Loading</span>
@ -126,13 +161,17 @@
<a class="btn btn-primary" href="/admin/nodes/edit/@(node.Id)"> <a class="btn btn-primary" href="/admin/nodes/edit/@(node.Id)">
<TL>Edit</TL> <TL>Edit</TL>
</a> </a>
<a class="btn btn-success" href="/admin/nodes/setup/@(node.Id)">
<TL>Setup</TL>
</a>
<WButton Text="@(SmartTranslateService.Translate("Delete"))" <WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))" WorkingText="@(SmartTranslateService.Translate("Deleting"))"
CssClasses="btn-danger" CssClasses="btn-danger"
OnClick="() => Delete(node)"></WButton> OnClick="() => Delete(node)">
</WButton>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
} }
@ -145,21 +184,80 @@
{ {
private Node[] Nodes; private Node[] Nodes;
private LazyLoader LazyLoader;
private Dictionary<Node, CpuStats> CpuCache = new(); private Dictionary<Node, CpuStats> CpuCache = new();
private Dictionary<Node, MemoryStats> MemoryCache = new(); private Dictionary<Node, MemoryStats> MemoryCache = new();
private Dictionary<Node, DiskStats> DiskCache = new(); private Dictionary<Node, DiskStats> DiskCache = new();
private Dictionary<Node, SystemStatus?> StatusCache = new();
private Task Load(LazyLoader lazyLoader) private Task Load(LazyLoader lazyLoader)
{ {
CpuCache.Clear();
MemoryCache.Clear();
DiskCache.Clear();
lock (StatusCache)
{
StatusCache.Clear();
}
Nodes = NodeRepository.Get().ToArray(); 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; 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();
}
}
} }
} }

View file

@ -16,31 +16,31 @@
<div class="d-flex flex-center flex-column-fluid"> <div class="d-flex flex-center flex-column-fluid">
<div class="form w-100 fv-plugins-bootstrap5 fv-plugins-framework"> <div class="form w-100 fv-plugins-bootstrap5 fv-plugins-framework">
<div class="fv-row mb-8"> <div class="fv-row mb-8">
<label> <label class="form-label">
<TL>Nodename</TL> <TL>Nodename</TL>
</label> </label>
<input @bind="NewNode.Name" type="text" placeholder="@(SmartTranslateService.Translate("Nodename"))" class="form-control bg-transparent"> <input @bind="NewNode.Name" type="text" placeholder="@(SmartTranslateService.Translate("Nodename"))" class="form-control bg-transparent">
</div> </div>
<div class="fv-row mb-8"> <div class="fv-row mb-8">
<label> <label class="form-label">
<TL>FQDN</TL> <TL>FQDN</TL>
</label> </label>
<input @bind="NewNode.Fqdn" type="text" placeholder="@(SmartTranslateService.Translate("FQDN"))" class="form-control bg-transparent"> <input @bind="NewNode.Fqdn" type="text" placeholder="@(SmartTranslateService.Translate("FQDN"))" class="form-control bg-transparent">
</div> </div>
<div class="fv-row mb-8"> <div class="fv-row mb-8">
<label> <label class="form-label">
<TL>Http port</TL> <TL>Http port</TL>
</label> </label>
<input @bind="NewNode.HttpPort" type="number" class="form-control bg-transparent"> <input @bind="NewNode.HttpPort" type="number" class="form-control bg-transparent">
</div> </div>
<div class="fv-row mb-8"> <div class="fv-row mb-8">
<label> <label class="form-label">
<TL>Sftp port</TL> <TL>Sftp port</TL>
</label> </label>
<input @bind="NewNode.SftpPort" type="number" class="form-control bg-transparent"> <input @bind="NewNode.SftpPort" type="number" class="form-control bg-transparent">
</div> </div>
<div class="fv-row mb-8"> <div class="fv-row mb-8">
<label> <label class="form-label">
<TL>Moonlight daemon port</TL> <TL>Moonlight daemon port</TL>
</label> </label>
<input @bind="NewNode.MoonlightDaemonPort" type="number" class="form-control bg-transparent"> <input @bind="NewNode.MoonlightDaemonPort" type="number" class="form-control bg-transparent">
@ -61,9 +61,12 @@
<div class="fv-row mb-9"> <div class="fv-row mb-9">
<WButton Text="@(SmartTranslateService.Translate("Create"))" <WButton Text="@(SmartTranslateService.Translate("Create"))"
WorkingText="@(SmartTranslateService.Translate("Creating"))" WorkingText="@(SmartTranslateService.Translate("Creating"))"
CssClasses="btn-primary" CssClasses="btn-success"
OnClick="Create"> OnClick="Create">
</WButton> </WButton>
<a href="/admin/nodes" class="btn btn-primary">
<TL>Back</TL>
</a>
</div> </div>
</div> </div>
</div> </div>

View 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/>
&nbsp;&nbsp;host: 0.0.0.0<br/>
&nbsp;&nbsp;port: @(Node.HttpPort)<br/>
&nbsp;&nbsp;ssl:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;enabled: false<br/>
&nbsp;&nbsp;&nbsp;&nbsp;cert: /etc/letsencrypt/live/@(Node.Fqdn)/fullchain.pem<br/>
&nbsp;&nbsp;&nbsp;&nbsp;key: /etc/letsencrypt/live/@(Node.Fqdn)/privkey.pem<br/>
&nbsp;&nbsp;disable_remote_download: false<br/>
&nbsp;&nbsp;upload_limit: 100<br/>
&nbsp;&nbsp;trusted_proxies: []<br/>
system:<br/>
&nbsp;&nbsp;root_directory: /var/lib/pterodactyl<br/>
&nbsp;&nbsp;log_directory: /var/log/pterodactyl<br/>
&nbsp;&nbsp;data: /var/lib/pterodactyl/volumes<br/>
&nbsp;&nbsp;archive_directory: /var/lib/pterodactyl/archives<br/>
&nbsp;&nbsp;backup_directory: /var/lib/pterodactyl/backups<br/>
&nbsp;&nbsp;tmp_directory: /tmp/pterodactyl<br/>
&nbsp;&nbsp;username: pterodactyl<br/>
&nbsp;&nbsp;timezone: Europe/Berlin<br/>
&nbsp;&nbsp;user:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;rootless:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;enabled: false<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container_uid: 0<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container_gid: 0<br/>
&nbsp;&nbsp;&nbsp;&nbsp;uid: 999<br/>
&nbsp;&nbsp;&nbsp;&nbsp;gid: 999<br/>
&nbsp;&nbsp;disk_check_interval: 150<br/>
&nbsp;&nbsp;activity_send_interval: 60<br/>
&nbsp;&nbsp;activity_send_count: 100<br/>
&nbsp;&nbsp;check_permissions_on_boot: true<br/>
&nbsp;&nbsp;enable_log_rotate: true<br/>
&nbsp;&nbsp;websocket_log_count: 150<br/>
&nbsp;&nbsp;sftp:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;bind_address: 0.0.0.0<br/>
&nbsp;&nbsp;&nbsp;&nbsp;bind_port: @(Node.SftpPort)<br/>
&nbsp;&nbsp;&nbsp;&nbsp;read_only: false<br/>
&nbsp;&nbsp;crash_detection:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;enabled: true<br/>
&nbsp;&nbsp;&nbsp;&nbsp;detect_clean_exit_as_crash: true<br/>
&nbsp;&nbsp;&nbsp;&nbsp;timeout: 60<br/>
&nbsp;&nbsp;backups:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;write_limit: 0<br/>
&nbsp;&nbsp;&nbsp;&nbsp;compression_level: best_speed<br/>
&nbsp;&nbsp;transfers:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;download_limit: 0<br/>
docker:<br/>
&nbsp;&nbsp;network:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;interface: 172.18.0.1<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dns:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;- 1.1.1.1<br/>
&nbsp;&nbsp;&nbsp;&nbsp;- 1.0.0.1<br/>
&nbsp;&nbsp;&nbsp;&nbsp;name: pterodactyl_nw<br/>
&nbsp;&nbsp;&nbsp;&nbsp;ispn: false<br/>
&nbsp;&nbsp;&nbsp;&nbsp;driver: bridge<br/>
&nbsp;&nbsp;&nbsp;&nbsp;network_mode: pterodactyl_nw<br/>
&nbsp;&nbsp;&nbsp;&nbsp;is_internal: false<br/>
&nbsp;&nbsp;&nbsp;&nbsp;enable_icc: true<br/>
&nbsp;&nbsp;&nbsp;&nbsp;network_mtu: 1500<br/>
&nbsp;&nbsp;&nbsp;&nbsp;interfaces:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v4:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;subnet: 172.18.0.0/16<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gateway: 172.18.0.1<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v6:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;subnet: fdba:17c8:6c94::/64<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gateway: fdba:17c8:6c94::1011<br/>
&nbsp;&nbsp;domainname: ""<br/>
&nbsp;&nbsp;registries: {}<br/>
&nbsp;&nbsp;tmpfs_size: 100<br/>
&nbsp;&nbsp;container_pid_limit: 512<br/>
&nbsp;&nbsp;installer_limits:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;memory: 1024<br/>
&nbsp;&nbsp;&nbsp;&nbsp;cpu: 100<br/>
&nbsp;&nbsp;overhead:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;override: false<br/>
&nbsp;&nbsp;&nbsp;&nbsp;default_multiplier: 1.05<br/>
&nbsp;&nbsp;&nbsp;&nbsp;multipliers: {}<br/>
&nbsp;&nbsp;use_performant_inspect: true<br/>
&nbsp;&nbsp;userns_mode: ""<br/>
&nbsp;&nbsp;log_config:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;type: local<br/>
&nbsp;&nbsp;&nbsp;&nbsp;config:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;compress: "false"<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;max-file: "1"<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;max-size: 5m<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mode: non-blocking<br/>
throttles:<br/>
&nbsp;&nbsp;enabled: true<br/>
&nbsp;&nbsp;lines: 2000<br/>
&nbsp;&nbsp;line_reset_interval: 100<br/>
remote: @(appUrl)<br/>
remote_query:<br/>
&nbsp;&nbsp;timeout: 30<br/>
&nbsp;&nbsp;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);
}
}

View 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();
}
}

View 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;
}
}

View 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")
);
}
}
}

View file

@ -1,9 +1,12 @@
@page "/" @page "/"
@using Moonlight.App.Repositories @using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Servers @using Moonlight.App.Repositories.Servers
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Services.Sessions
@inject DatabaseRepository DatabaseRepository @inject DatabaseRepository DatabaseRepository
@inject ServerRepository ServerRepository @inject ServerRepository ServerRepository
@inject IdentityService IdentityService
<LazyLoader Load="Load"> <LazyLoader Load="Load">
<div class="row mb-5"> <div class="row mb-5">
@ -222,11 +225,18 @@
private int DatabaseCount = 0; private int DatabaseCount = 0;
private int ServerCount = 0; private int ServerCount = 0;
private Task Load(LazyLoader lazyLoader) private async Task Load(LazyLoader lazyLoader)
{ {
DatabaseCount = DatabaseRepository.Get().Count(); var user = await IdentityService.Get();
ServerCount = ServerRepository.Get().Count();
DatabaseCount = DatabaseRepository
return Task.CompletedTask; .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);
} }
} }

View 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");
}
}
}

View 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);
}
}
}

View file

@ -1,10 +1,9 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// Dieser Code wurde von einem Tool generiert. // This code was generated by a tool.
// Laufzeitversion:4.0.30319.42000
// //
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn // Changes to this file may cause incorrect behavior and will be lost if
// der Code erneut generiert wird. // the code is regenerated.
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View file

@ -51,6 +51,14 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcRXJyb3JCb3VuZGFyaWVzXFBhZ2VFcnJvckJvdW5kYXJ5LnJhem9y build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcRXJyb3JCb3VuZGFyaWVzXFBhZ2VFcnJvckJvdW5kYXJ5LnJhem9y
build_metadata.AdditionalFiles.CssScope = 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] [C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/Forms/WButton.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcRm9ybXNcV0J1dHRvbi5yYXpvcg== build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcRm9ybXNcV0J1dHRvbi5yYXpvcg==
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
@ -87,6 +95,50 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcUGFydGlhbHNcVEwucmF6b3I= build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcUGFydGlhbHNcVEwucmF6b3I=
build_metadata.AdditionalFiles.CssScope = 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] [C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/StateLogic/IsSetup.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU3RhdGVMb2dpY1xJc1NldHVwLnJhem9y build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU3RhdGVMb2dpY1xJc1NldHVwLnJhem9y
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
@ -99,6 +151,10 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU3RhdGVMb2dpY1xPbmx5QWRtaW4ucmF6b3I= build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU3RhdGVMb2dpY1xPbmx5QWRtaW4ucmF6b3I=
build_metadata.AdditionalFiles.CssScope = 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] [C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Layouts/MainLayout.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXExheW91dHNcTWFpbkxheW91dC5yYXpvcg== build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXExheW91dHNcTWFpbkxheW91dC5yYXpvcg==
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
@ -123,10 +179,34 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXE5vZGVzXE5ldy5yYXpvcg== build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXE5vZGVzXE5ldy5yYXpvcg==
build_metadata.AdditionalFiles.CssScope = 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] [C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Views/Index.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEluZGV4LnJhem9y build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEluZGV4LnJhem9y
build_metadata.AdditionalFiles.CssScope = 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] [C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Views/Setup/Features.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXFNldHVwXEZlYXR1cmVzLnJhem9y build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXFNldHVwXEZlYXR1cmVzLnJhem9y
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =

View file

@ -1,10 +1,9 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// Dieser Code wurde von einem Tool generiert. // This code was generated by a tool.
// Laufzeitversion:4.0.30319.42000
// //
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn // Changes to this file may cause incorrect behavior and will be lost if
// der Code erneut generiert wird. // the code is regenerated.
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View file

@ -60,12 +60,16 @@
}, },
"BlazorMonaco": { "BlazorMonaco": {
"target": "Package", "target": "Package",
"version": "[3.0.0, )" "version": "[2.1.0, )"
}, },
"BlazorTable": { "BlazorTable": {
"target": "Package", "target": "Package",
"version": "[1.17.0, )" "version": "[1.17.0, )"
}, },
"Blazored.Typeahead": {
"target": "Package",
"version": "[4.7.0, )"
},
"CurrieTechnologies.Razor.SweetAlert2": { "CurrieTechnologies.Razor.SweetAlert2": {
"target": "Package", "target": "Package",
"version": "[5.4.0, )" "version": "[5.4.0, )"

View file

@ -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)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)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)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')" /> <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> </ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> <PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">

View file

@ -56,17 +56,36 @@
"buildMultiTargeting/Blazor.ContextMenu.props": {} "buildMultiTargeting/Blazor.ContextMenu.props": {}
} }
}, },
"BlazorMonaco/3.0.0": { "Blazored.Typeahead/4.7.0": {
"type": "package", "type": "package",
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Components": "6.0.0", "Microsoft.AspNetCore.Components": "6.0.3",
"Microsoft.AspNetCore.Components.Web": "6.0.0" "Microsoft.AspNetCore.Components.Web": "6.0.3"
}, },
"compile": { "compile": {
"lib/net6.0/BlazorMonaco.dll": {} "lib/net6.0/Blazored.Typeahead.dll": {}
}, },
"runtime": { "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": { "build": {
"buildTransitive/BlazorMonaco.props": {} "buildTransitive/BlazorMonaco.props": {}
@ -960,15 +979,33 @@
"staticwebassets/blazorContextMenu.min.js" "staticwebassets/blazorContextMenu.min.js"
] ]
}, },
"BlazorMonaco/3.0.0": { "Blazored.Typeahead/4.7.0": {
"sha512": "GD2IJR4ke05Hj5koO2Mk4cEToDZI7dXnDhtb8H8eqMiCjlROX1DGBSt3K1c2C14mBtFGH4swJCgmHBDxU1R+MQ==", "sha512": "fTN4Bt9rwEE/d33FXFd+h/DBjVtsFJLRf9B8mu0zJWMWG5Mk2tU2il3aK0+laUxNgBNBgEL0jW/831I+oapd6A==",
"type": "package", "type": "package",
"path": "blazormonaco/3.0.0", "path": "blazored.typeahead/4.7.0",
"files": [ "files": [
".nupkg.metadata", ".nupkg.metadata",
".signature.p7s", ".signature.p7s",
"README.md", "blazored.typeahead.4.7.0.nupkg.sha512",
"blazormonaco.3.0.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", "blazormonaco.nuspec",
"build/BlazorMonaco.props", "build/BlazorMonaco.props",
"build/Microsoft.AspNetCore.StaticWebAssets.props", "build/Microsoft.AspNetCore.StaticWebAssets.props",
@ -976,20 +1013,8 @@
"buildTransitive/BlazorMonaco.props", "buildTransitive/BlazorMonaco.props",
"icon.png", "icon.png",
"lib/net5.0/BlazorMonaco.dll", "lib/net5.0/BlazorMonaco.dll",
"lib/net6.0/BlazorMonaco.dll",
"lib/net7.0/BlazorMonaco.dll",
"lib/netstandard2.0/BlazorMonaco.dll", "lib/netstandard2.0/BlazorMonaco.dll",
"staticwebassets/jsInterop.js", "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/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.js.map",
"staticwebassets/lib/monaco-editor/min-maps/vs/editor/editor.main.nls.de.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/editor/editor.main.nls.zh-tw.js.map",
"staticwebassets/lib/monaco-editor/min-maps/vs/loader.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/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.js",
"staticwebassets/lib/monaco-editor/min/vs/base/worker/workerMain.min.js", "staticwebassets/lib/monaco-editor/min/vs/base/worker/workerMain.min.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/abap/abap.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/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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/bat/bat.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/cameligo/cameligo.min.js", "staticwebassets/lib/monaco-editor/min/vs/basic-languages/cameligo/cameligo.min.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/clojure/clojure.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/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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/css/css.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/dart/dart.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/dockerfile/dockerfile.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/ecl/ecl.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/fsharp/fsharp.min.js", "staticwebassets/lib/monaco-editor/min/vs/basic-languages/fsharp/fsharp.min.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/go/go.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/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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/lexon/lexon.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/lua/lua.min.js", "staticwebassets/lib/monaco-editor/min/vs/basic-languages/lua/lua.min.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/m3/m3.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/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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/php/php.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/postiats/postiats.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/powerquery/powerquery.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/powershell/powershell.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/pug/pug.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/python/python.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/r/r.min.js", "staticwebassets/lib/monaco-editor/min/vs/basic-languages/r/r.min.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/razor/razor.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/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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/sophia/sophia.min.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.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/sql/sql.min.js", "staticwebassets/lib/monaco-editor/min/vs/basic-languages/sql/sql.min.js",
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/st/st.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.css",
"staticwebassets/lib/monaco-editor/min/vs/editor/editor.main.js", "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.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.js",
"staticwebassets/lib/monaco-editor/min/vs/editor/editor.main.nls.de.min.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", "staticwebassets/lib/monaco-editor/min/vs/editor/editor.main.nls.es.js",
@ -2793,8 +2777,9 @@
"BCrypt.Net-Next >= 4.0.3", "BCrypt.Net-Next >= 4.0.3",
"Ben.Demystifier >= 0.4.1", "Ben.Demystifier >= 0.4.1",
"Blazor.ContextMenu >= 1.15.0", "Blazor.ContextMenu >= 1.15.0",
"BlazorMonaco >= 3.0.0", "BlazorMonaco >= 2.1.0",
"BlazorTable >= 1.17.0", "BlazorTable >= 1.17.0",
"Blazored.Typeahead >= 4.7.0",
"CurrieTechnologies.Razor.SweetAlert2 >= 5.4.0", "CurrieTechnologies.Razor.SweetAlert2 >= 5.4.0",
"Discord.Net >= 3.9.0", "Discord.Net >= 3.9.0",
"GravatarSharp.Core >= 1.0.1.2", "GravatarSharp.Core >= 1.0.1.2",
@ -2876,12 +2861,16 @@
}, },
"BlazorMonaco": { "BlazorMonaco": {
"target": "Package", "target": "Package",
"version": "[3.0.0, )" "version": "[2.1.0, )"
}, },
"BlazorTable": { "BlazorTable": {
"target": "Package", "target": "Package",
"version": "[1.17.0, )" "version": "[1.17.0, )"
}, },
"Blazored.Typeahead": {
"target": "Package",
"version": "[4.7.0, )"
},
"CurrieTechnologies.Razor.SweetAlert2": { "CurrieTechnologies.Razor.SweetAlert2": {
"target": "Package", "target": "Package",
"version": "[5.4.0, )" "version": "[5.4.0, )"

View file

@ -1,6 +1,6 @@
{ {
"version": 2, "version": 2,
"dgSpecHash": "pQgIHNaG7q9d70t4chS097wnXaBcGVs4M1KNaxW4kyPiCyrsd3TNRJ260ThoX5FTLByZYWNfqf9FNIzWLSOXTQ==", "dgSpecHash": "tg/pcbZFjC1DzdXLz1U4t3D9RlK8DsBcivE40sezA9KD921KFGrw4MATZDqmNFjOQaQFeekGMS+4SukWB5eJWA==",
"success": true, "success": true,
"projectFilePath": "C:\\Users\\marce\\source\\repos\\MoonlightPublic\\Moonlight\\Moonlight\\Moonlight.csproj", "projectFilePath": "C:\\Users\\marce\\source\\repos\\MoonlightPublic\\Moonlight\\Moonlight\\Moonlight.csproj",
"expectedPackageFiles": [ "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\\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\\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\\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\\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\\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", "C:\\Users\\marce\\.nuget\\packages\\discord.net\\3.9.0\\discord.net.3.9.0.nupkg.sha512",

View file

@ -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"}}

View file

@ -119,7 +119,77 @@ Add a new node;Add a new node
Delete;Delete Delete;Delete
Deleting;Deleting Deleting;Deleting
Edit;Edit Edit;Edit
Toekn Id;Toekn Id Token Id;Token Id
Token;Token Token;Token
Save;Save 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

View file

@ -6,4 +6,12 @@
.invisible-a:hover { .invisible-a:hover {
color: inherit; color: inherit;
cursor: pointer; cursor: pointer;
}
.blur-unless-hover {
filter: blur(5px);
}
.blur-unless-hover:hover {
filter: none;
} }

View file

@ -1 +0,0 @@
var require = { paths: { vs: '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } };