Fixed smaller issues. Upgraded server node ui

This commit is contained in:
Marcel Baumgartner
2024-06-26 10:59:14 +02:00
parent 95a5eafec2
commit 53509ecf63
8 changed files with 257 additions and 121 deletions

View File

@@ -169,8 +169,6 @@ else
if (TryGetAttribute(customAttributes, out DisplayNameAttribute nameAttribute))
propConfig.WithName(nameAttribute.DisplayName);
else
propConfig.WithName(Formatter.ConvertCamelCaseToSpaces(property.Name));
if (TryGetAttribute(customAttributes, out DescriptionAttribute descriptionAttribute))
propConfig.WithDescription(descriptionAttribute.Description);

View File

@@ -53,7 +53,7 @@
private void OnConfigure(FastCrudConfiguration<User> configuration)
{
configuration.CustomCreate += async user =>
configuration.CustomCreate = async user =>
{
var result = await AuthenticationProvider.Register(user.Username, user.Email, user.Password);
@@ -61,7 +61,7 @@
throw new DisplayException("An unknown error occured while creating user");
};
configuration.ValidateEdit += async user =>
configuration.ValidateEdit = async user =>
{
await AuthenticationProvider.ChangeDetails(user, user.Email, user.Username);
};

View File

@@ -3,7 +3,7 @@ namespace Moonlight.Features.Servers.Entities;
public class ServerAllocation
{
public int Id { get; set; }
public string IpAddress { get; set; } = "";
public string IpAddress { get; set; } = "0.0.0.0";
public int Port { get; set; }
public string Note { get; set; } = "";
}

View File

@@ -1,7 +1,10 @@
@using System.ComponentModel.DataAnnotations
@using Moonlight.Features.Servers.Entities
@using Moonlight.Features.Servers.Models.Forms.Admin.Nodes
@using BlazorTable
@using MoonCore.Abstractions
@using MoonCore.Blazor.Models.Fast
@using MoonCore.Blazor.Models.Fast.Validators
@using MoonCore.Exceptions
@@ -27,38 +30,31 @@
<input @bind="AllocationEnd" class="form-control w-25" type="number"/>
</div>
<div class="d-flex justify-content-end">
<WButton OnClick="AddAllocations" Text="Add" CssClasses="btn btn-primary"/>
<WButton OnClick="AddAllocations" CssClasses="btn btn-primary">
Add
</WButton>
</div>
</div>
</div>
</div>
<div class="col-md-8 col-12">@*
<AutoListCrud TRootItem="ServerNode"
TItem="ServerAllocation"
TCreateForm="CreateAllocationForm"
TUpdateForm="UpdateAllocationForm"
Title=""
Field="@(x => x.Allocations)"
RootItem="Node"
@ref="Crud"
ValidateDelete="ValidateDelete"
ValidateAdd="ValidateAdd">
<Toolbar>
<WButton CssClasses="btn btn-icon btn-danger" OnClick="DeleteAllAllocations">
<div class="col-md-8 col-12">
<FastCrud TItem="ServerAllocation"
Loader="Loader"
OnConfigure="OnConfigure"
OnConfigureCreate="OnConfigureCreate"
OnConfigureEdit="OnConfigureEdit"
@ref="Crud">
<View>
<MCBColumn TItem="ServerAllocation" Field="@(x => x.Id)" Title="Id"/>
<MCBColumn TItem="ServerAllocation" Field="@(x => x.IpAddress)" Title="Ip address"/>
<MCBColumn TItem="ServerAllocation" Field="@(x => x.Port)" Title="Port"/>
</View>
<ViewToolbar>
<WButton CssClasses="btn btn-icon btn-danger me-2" OnClick="DeleteAllAllocations">
<i class="bx bx-sm bx-trash"></i>
</WButton>
</Toolbar>
<View>
<CrudColumn TItem="ServerAllocation" Field="@(x => x.Id)" Title="Id"/>
<CrudColumn TItem="ServerAllocation" Field="@(x => x.IpAddress)" Title="Ip address"/>
<CrudColumn TItem="ServerAllocation" Field="@(x => x.Port)" Title="Port"/>
</View>
<NoItemsView>
<IconAlert Title="No allocations found" Color="primary" Icon="bx-search-alt">
In order for a server to be deployed on this node allocations need to be created here
</IconAlert>
</NoItemsView>
</AutoListCrud>*@
</ViewToolbar>
</FastCrud>
</div>
</div>
@@ -66,8 +62,7 @@
{
[Parameter] public ServerNode Node { get; set; }
// A bit long, lol
//private AutoListCrud<ServerAllocation, ServerNode, CreateAllocationForm, UpdateAllocationForm> Crud;
private FastCrud<ServerAllocation> Crud;
// Quick add values
private string IpAddress = "0.0.0.0";
@@ -101,7 +96,7 @@
NodeRepository.Update(Node!);
await ToastService.Success($"Added {added} allocations and skipped {skipped} ports due to existing allocations");
//await Crud.Reload();
await Crud.Refresh();
}
private async Task DeleteAllAllocations()
@@ -124,30 +119,91 @@
}
await ToastService.Success("Successfully deleted allocations");
//await Crud.Reload();
await Crud.Refresh();
});
}
private Task ValidateDelete(ServerAllocation allocation)
private IEnumerable<ServerAllocation> Loader(Repository<ServerAllocation> _)
{
// Check if allocation is associated with a server
var serverWithThisAllocation = ServerRepository
.Get()
.FirstOrDefault(x => x.Allocations.Any(y => y.Id == allocation.Id));
return Node.Allocations;
}
if (serverWithThisAllocation != null)
private void OnConfigure(FastCrudConfiguration<ServerAllocation> configuration)
{
configuration.ValidateCreate = allocation =>
{
throw new DisplayException($"The server '{serverWithThisAllocation.Name}' (ID: {serverWithThisAllocation.Id}) is using this allocation. Delete the server in order to delete this allocation");
}
if (Node.Allocations.Any(x => x.Port == allocation.Port && x.IpAddress == allocation.IpAddress))
throw new DisplayException("A allocation with these ip and port does already exist");
return Task.CompletedTask;
return Task.CompletedTask;
};
configuration.ValidateEdit = allocation =>
{
if (Node.Allocations.Any(x => x.Port == allocation.Port && x.IpAddress == allocation.IpAddress && x.Id != allocation.Id))
throw new DisplayException("A allocation with these ip and port does already exist");
return Task.CompletedTask;
};
configuration.ValidateDelete = allocation =>
{
// Check if allocation is associated with a server
var serverWithThisAllocation = ServerRepository
.Get()
.FirstOrDefault(x => x.Allocations.Any(y => y.Id == allocation.Id));
if (serverWithThisAllocation != null)
{
throw new DisplayException($"The server '{serverWithThisAllocation.Name}' (ID: {serverWithThisAllocation.Id}) is using this allocation. Delete the server in order to delete this allocation");
}
return Task.CompletedTask;
};
configuration.CustomCreate = allocation =>
{
Node.Allocations.Add(allocation);
NodeRepository.Update(Node);
return Task.CompletedTask;
};
configuration.CustomEdit = allocation =>
{
AllocationRepository.Update(allocation);
return Task.CompletedTask;
};
configuration.CustomDelete = allocation =>
{
Node.Allocations.Remove(allocation);
NodeRepository.Update(Node);
try
{
AllocationRepository.Delete(allocation);
}
catch (Exception)
{
/* do not fail here */
}
return Task.CompletedTask;
};
}
private Task ValidateAdd(ServerAllocation allocation)
private void OnConfigureCreate(FastConfiguration<ServerAllocation> configuration)
{
if (Node!.Allocations.Any(x => x.Port == allocation.Port && x.IpAddress == allocation.IpAddress))
throw new DisplayException("A allocation with these ip and port does already exist");
configuration.AddProperty(x => x.IpAddress)
.WithDefaultComponent()
.WithValidation(FastValidators.Required)
.WithValidation(RegexValidator.Create("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", "You need to provide a valid ipv4 address"));
return Task.CompletedTask;
configuration.AddProperty(x => x.Port)
.WithDefaultComponent()
.WithValidation<int>(x => x >= 1 && x <= 65535 ? ValidationResult.Success : new ValidationResult("You need to provide a valid port"));
}
private void OnConfigureEdit(FastConfiguration<ServerAllocation> configuration, ServerAllocation _) => OnConfigureCreate(configuration);
}

View File

@@ -8,7 +8,7 @@
@implements IDisposable
<LazyLoader Load="Load">
<div class="row g-5 mt-5">
<div class="row g-5">
<div class="col-md-3 col-12">
@{
var cpuName = Status.Hardware.Cores.Any() ? Status.Hardware.Cores.First().Name : "N/A";

View File

@@ -1,5 +1,6 @@
@page "/admin/servers/nodes"
@using System.ComponentModel.DataAnnotations
@using Moonlight.Features.Servers.Models.Forms.Admin.Nodes
@using Moonlight.Features.Servers.UI.Components
@using Moonlight.Features.Servers.Entities
@@ -8,8 +9,11 @@
@using MoonCore.Exceptions
@using MoonCore.Helpers
@using System.Text.RegularExpressions;
@using MoonCore.Blazor.Forms.Fast.Components
@using MoonCore.Blazor.Models.Fast
@using Moonlight.Features.Servers.Api.Resources
@using Moonlight.Features.Servers.Services
@using Moonlight.Features.Servers.UI.NodeComponents
@inject Repository<Server> ServerRepository
@inject Repository<ServerNode> NodeRepository
@@ -24,18 +28,18 @@
<AdminServersNavigation Index="1"/>
<LazyLoader Load="Load">
<AutoCrud TItem="ServerNode"
TCreateForm="CreateNodeForm"
TUpdateForm="UpdateNodeForm"
Loader="LoadData"
ValidateAdd="ValidateAdd"
ValidateUpdate="ValidateUpdate"
ValidateDelete="ValidateDelete">
<FastCrud TItem="ServerNode"
Loader="Loader"
OnConfigure="OnConfigure"
OnConfigureCreate="OnConfigureCreate"
OnConfigureEdit="OnConfigureEdit">
<View>
<MCBColumn TItem="ServerNode" Field="@(x => x.Id)" Title="Id"/>
<MCBColumn TItem="ServerNode" Field="@(x => x.Name)" Title="Name">
<Template>
<a href="/admin/servers/nodes/view/@(context!.Id)">@context!.Name</a>
@*<a href="/admin/servers/nodes/view/@(context!.Id)"></a> TODO: Make this work again*@
@context!.Name
</Template>
</MCBColumn>
<MCBColumn TItem="ServerNode" Field="@(x => x.Fqdn)" Title="Fqdn"/>
@@ -63,7 +67,7 @@
@if (NodeStats.ContainsKey(context!.Id) && NodeStats[context.Id] != null)
{
var cores = NodeStats[context!.Id]!.Hardware.Cores;
var percent = cores.Any() ? Math.Round(cores.Average(x => x.Usage), 2) : 0;
var percent = cores.Any() ? Math.Round(cores.Average(x => x.Usage), 2) : 0;
<ColoredBar Value="percent"/>
}
@@ -80,7 +84,7 @@
var memory = NodeStats[context!.Id]!.Hardware.Memory;
var used = memory.Total - (memory.Available + memory.Cached);
var percent = Math.Round((float) used / memory.Total * 100F, 2);
var percent = Math.Round((float)used / memory.Total * 100F, 2);
//Logger.Debug($"Used: {used} Total: {memory.Total} => {percent}% ({Formatter.FormatSize(used)} / {Formatter.FormatSize(memory.Total)})");
@@ -108,13 +112,7 @@
</Template>
</MCBColumn>
</View>
@*
<NoItemsView>
<IconAlert Title="No nodes found" Color="primary" Icon="bx-search-alt">
Add a new node in order to get started. Need help? Check out our <a href="https://docs.moonlightpanel.xyz">documentation</a>
</IconAlert>
</NoItemsView>*@
</AutoCrud>
</FastCrud>
</LazyLoader>
@code
@@ -126,76 +124,160 @@
{
UpdateTimer = new(async _ =>
{
NodeStats.Clear();
using var scope = ServiceProvider.CreateScope();
var nodeRepo = scope.ServiceProvider.GetRequiredService<Repository<ServerNode>>();
var nodes = nodeRepo.Get().ToArray();
foreach (var node in nodes)
try
{
try
NodeStats.Clear();
using var scope = ServiceProvider.CreateScope();
var nodeRepo = scope.ServiceProvider.GetRequiredService<Repository<ServerNode>>();
var nodes = nodeRepo.Get().ToArray();
foreach (var node in nodes)
{
var status = await NodeService.GetStatus(node);
try
{
var status = await NodeService.GetStatus(node);
NodeStats[node.Id] = status;
NodeStats[node.Id] = status;
}
catch (Exception e)
{
Logger.LogWarning("Unable to fetch system status for node '{name}': {e}", node.Name, e);
NodeStats[node.Id] = null;
}
await InvokeAsync(StateHasChanged);
}
catch (Exception e)
{
Logger.LogWarning("Unable to fetch system status for node '{name}': {e}", node.Name, e);
NodeStats[node.Id] = null;
}
await InvokeAsync(StateHasChanged);
}
catch (Exception e)
{
Logger.LogError("Unable to update node stats due to an unhandled error: {e}", e);
}
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
return Task.CompletedTask;
}
private IEnumerable<ServerNode> LoadData(Repository<ServerNode> repository)
private IEnumerable<ServerNode> Loader(Repository<ServerNode> repository)
{
return repository
.Get();
}
private Task ValidateDelete(ServerNode node)
{
if (ServerRepository
.Get()
.Any(x => x.Node.Id == node.Id))
{
throw new DisplayException("There are still servers on this node. Delete the servers in order to delete the node");
}
if (NodeRepository
.Get()
.Include(x => x.Allocations)
.First(x => x.Id == node.Id)
.Allocations
.Any())
{
throw new DisplayException("There are still allocations on this node. Delete the allocations in order to delete the node");
}
return Task.CompletedTask;
.Include(x => x.Allocations);
}
private Task ValidateAdd(ServerNode node)
private void OnConfigure(FastCrudConfiguration<ServerNode> configuration)
{
ValidateFqdn(node);
configuration.ValidateDelete = node =>
{
if (ServerRepository
.Get()
.Any(x => x.Node.Id == node.Id))
{
throw new DisplayException("There are still servers on this node. Delete the servers in order to delete the node");
}
node.Token = Formatter.GenerateString(32);
if (NodeRepository
.Get()
.Include(x => x.Allocations)
.First(x => x.Id == node.Id)
.Allocations
.Any())
{
throw new DisplayException("There are still allocations on this node. Delete the allocations in order to delete the node");
}
return Task.CompletedTask;
return Task.CompletedTask;
};
configuration.ValidateCreate = node =>
{
ValidateFqdn(node);
node.Token = Formatter.GenerateString(32);
return Task.CompletedTask;
};
configuration.ValidateEdit = node =>
{
ValidateFqdn(node);
return Task.CompletedTask;
};
}
private Task ValidateUpdate(ServerNode node)
private void OnConfigureCreate(FastConfiguration<ServerNode> configuration)
{
ValidateFqdn(node);
configuration.AddProperty(x => x.Name)
.WithDefaultComponent()
.WithValidation(FastValidators.Required);
return Task.CompletedTask;
configuration.AddProperty(x => x.Fqdn)
.WithDefaultComponent()
.WithDescription("This needs to be the ip or domain of the node depending on the ssl settings")
.WithValidation(FastValidators.Required);
configuration.AddProperty(x => x.Ssl)
.WithComponent<bool, SwitchComponent>()
.WithDescription("This enables ssl for the http connections to the node. Only enable this if you have the cert installed on the node");
configuration.AddProperty(x => x.HttpPort)
.WithDefaultComponent()
.WithDescription("This is the http(s) port used by the node to allow communication to the node from the panel");
configuration.AddProperty(x => x.FtpPort)
.WithDefaultComponent()
.WithDescription("This is the ftp port users can use to access their servers filesystem via their ftp client");
}
private void OnConfigureEdit(FastConfiguration<ServerNode> configuration, ServerNode node)
{
configuration.AddProperty(x => x.Name)
.WithDefaultComponent()
.WithPage("Settings")
.WithValidation(FastValidators.Required);
configuration.AddProperty(x => x.Fqdn)
.WithDefaultComponent()
.WithPage("Settings")
.WithDescription("This needs to be the ip or domain of the node depending on the ssl settings")
.WithValidation(FastValidators.Required);
configuration.AddProperty(x => x.Ssl)
.WithComponent<bool, SwitchComponent>()
.WithPage("Settings")
.WithDescription("This enables ssl for the http connections to the node. Only enable this if you have the cert installed on the node");
configuration.AddProperty(x => x.HttpPort)
.WithDefaultComponent()
.WithPage("Settings")
.WithDescription("This is the http(s) port used by the node to allow communication to the node from the panel");
configuration.AddProperty(x => x.FtpPort)
.WithDefaultComponent()
.WithPage("Settings")
.WithDescription("This is the ftp port users can use to access their servers filesystem via their ftp client");
configuration.AddCustomPage("Overview", ComponentHelper.FromType<NodeOverview>(parameters =>
{
parameters.Add("Node", node);
}));
configuration.AddCustomPage("Allocations", ComponentHelper.FromType<NodeAllocations>(parameters =>
{
parameters.Add("Node", node);
}));
configuration.AddCustomPage("Setup", ComponentHelper.FromType<NodeSetup>(parameters =>
{
parameters.Add("Node", node);
}));
configuration.AddCustomPage("Logs", ComponentHelper.FromType<NodeLogs>(parameters =>
{
parameters.Add("Node", node);
}));
}
private void ValidateFqdn(ServerNode node)

View File

@@ -74,7 +74,7 @@
private void OnConfigure(FastCrudConfiguration<ServerNetwork> configuration)
{
configuration.ValidateCreate += network =>
configuration.ValidateCreate = network =>
{
if (!ServerRepository
.Get()

View File

@@ -94,7 +94,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MoonCore" Version="1.4.1" />
<PackageReference Include="MoonCore.Blazor" Version="1.0.9" />
<PackageReference Include="MoonCore.Blazor" Version="1.1.0" />
<PackageReference Include="Otp.NET" Version="1.3.0" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />