Files
Moonlight/Moonlight/Features/Servers/UI/Views/Admin/Index.razor
2024-07-05 20:09:45 +02:00

257 lines
9.3 KiB
Plaintext

@page "/admin/servers"
@using System.ComponentModel.DataAnnotations
@using Moonlight.Features.Servers.UI.Components
@using Microsoft.EntityFrameworkCore
@using MoonCore.Abstractions
@using MoonCore.Exceptions
@using Moonlight.Core.Database.Entities
@using Moonlight.Features.Servers.Entities
@using Moonlight.Features.Servers.Models.Enums
@using Moonlight.Features.Servers.Services
@inject ServerService ServerService
@inject Repository<Server> ServerRepository
@inject Repository<ServerAllocation> AllocRepository
@inject ToastService ToastService
@inject AlertService AlertService
@inject ILogger<Index> Logger
@attribute [RequirePermission(5000)]
<AdminServersNavigation Index="0"/>
<FastCrud TItem="Server"
Loader="Loader"
OnConfigure="OnConfigure"
OnConfigureCreate="OnConfigureCreate"
OnConfigureEdit="OnConfigureEdit"
@ref="Crud">
<View>
<MCBColumn TItem="Server" Field="@(x => x.Id)" Title="Id" Filterable="true"/>
<MCBColumn TItem="Server" Field="@(x => x.Name)" Title="Name" Filterable="true"/>
<MCBColumn TItem="Server" Field="@(x => x.Id)" Title="Image">
<Template>
<span>@(context.Image.Name)</span>
</Template>
</MCBColumn>
<MCBColumn TItem="Server" Field="@(x => x.Id)" Title="Node">
<Template>
<span>@(context.Node.Name)</span>
</Template>
</MCBColumn>
<MCBColumn TItem="Server" Field="@(x => x.Id)" Title="User">
<Template>
<span>@(context.Owner.Username)</span>
</Template>
</MCBColumn>
</View>
<EditToolbar>
<WButton CssClasses="btn btn-danger me-2" OnClick="() => StartForceDelete(context)">
<i class="bx bx-sm bx-bomb"></i>
Force delete
</WButton>
</EditToolbar>
</FastCrud>
@code
{
private FastCrud<Server> Crud;
private async Task CustomUpdate(Server server)
{
ServerRepository.Update(server);
try
{
// Let the daemon know we changed this server
await ServerService.Sync(server);
// Check if the server is running to let the user know if he needs to restart the
// server. This should prevent the confusion why a running server does not get the changes applied
// ... hopefully ;)
try
{
if (await ServerService.GetState(server) == ServerState.Offline)
return;
await ToastService.Info("Server is currently running. It requires a restart of the server in order to apply the changes");
}
catch (Exception)
{
// ignore, sync has already happened
}
}
catch (Exception e)
{
Logger.LogError("Unable to sync server changes due to an error occuring: {e}", e);
await ToastService.Danger("An error occured while sending the changes to the daemon");
}
}
private Task ValidateUpdate(Server server)
{
var oldServer = ServerRepository
.Get()
.Include(x => x.Image)
.First(x => x.Id == server.Id);
// Virtual disk check
if (oldServer.UseVirtualDisk != server.UseVirtualDisk)
throw new DisplayException("Unable to switch from/to virtual disks. This is not supported at the moment");
// Allocation amount check
if (server.Allocations.Count < oldServer.Image.AllocationsNeeded)
throw new DisplayException($"The server image requires at least {oldServer.Image.AllocationsNeeded} allocation(s) in order to work");
// Set the correct main allocation
server.MainAllocation = server.Allocations.First();
// Check for image changes
if (oldServer.Image.Id != server.Image.Id)
throw new DisplayException("Changing the server image has not been implemented yet");
return Task.CompletedTask;
}
private async Task StartForceDelete(Server server)
{
await AlertService.Confirm(
"Confirm forcefully server deletion",
"Do you really want to delete this server forcefully?",
async () =>
{
await ServerService.Delete(server, safeDelete: false);
await ToastService.Success("Successfully deleted server");
await Crud.SetState(FastCrudState.View);
}
);
}
private IEnumerable<Server> Loader(Repository<Server> repository)
{
return repository
.Get()
.Include(x => x.Image)
.Include(x => x.Node)
.Include(x => x.Owner)
.Include(x => x.Allocations);
}
private void OnConfigure(FastCrudConfiguration<Server> configuration)
{
configuration.CustomCreate = ServerService.Create;
configuration.CustomDelete = server => ServerService.Delete(server);
configuration.CustomEdit = CustomUpdate;
configuration.ValidateEdit = ValidateUpdate;
}
// Shared form
private void OnConfigureBase(FastFormConfiguration<Server> configuration, Server server)
{
configuration.AddProperty(x => x.Name)
.WithDefaultComponent()
.WithValidation(FastFormValidators.Required);
configuration.AddProperty(x => x.Owner)
.WithComponent<DropdownComponent<User>>(component =>
{
component.SearchFunc = x => x.Username;
component.DisplayFunc = x => x.Username;
})
.WithValidation(FastFormValidators.Required);
configuration.AddProperty(x => x.Image)
.WithComponent<DropdownComponent<ServerImage>>(component =>
{
component.SearchFunc = x => x.Name;
component.DisplayFunc = x => x.Name;
})
.WithValidation(FastFormValidators.Required);
configuration.AddProperty(x => x.Cpu)
.WithDefaultComponent()
.WithValidation(x => x > 0 ? ValidationResult.Success : new("You need to provide a valid value"))
.WithSection("Resources", "bxs-chip")
.WithDescription("The cores the server will be able to use. 100 = 1 Core");
configuration.AddProperty(x => x.Memory)
.WithComponent<ByteSizeComponent>(component =>
{
component.MinimumUnit = "MB";
component.DefaultUnit = "GB";
component.Converter = 1;
})
.WithValidation(x => x > 0 ? ValidationResult.Success : new("You need to provide a valid value"))
.WithSection("Resources")
.WithDescription("The amount of memory this server will be able to use");
configuration.AddProperty(x => x.Disk)
.WithComponent<ByteSizeComponent>(component =>
{
component.MinimumUnit = "MB";
component.DefaultUnit = "GB";
component.Converter = 1;
})
.WithValidation(x => x > 0 ? ValidationResult.Success : new("You need to provide a valid value"))
.WithSection("Resources")
.WithDescription("The amount of disk space this server will be able to use");
configuration.AddProperty(x => x.UseVirtualDisk)
.WithComponent<SwitchComponent>()
.WithPage("Advanced options")
.WithDescription("Whether to use a virtual disk for storing server files. Dont use this if you want to overallocate as the virtual disks will fill out the space you allocate");
configuration.AddProperty(x => x.DisablePublicNetwork)
.WithComponent<SwitchComponent>()
.WithPage("Network")
.WithDescription("Whether to block all incoming connections to this server from the internet");
configuration.AddProperty(x => x.Allocations)
.WithComponent<MultiSelectComponent<ServerAllocation>>(component =>
{
component.SearchFunc = x => $"{x.IpAddress}:{x.Port}";
component.DisplayFunc = x => $"{x.IpAddress}:{x.Port}";
component.ItemsCallback = () =>GetAllocation(server);
component.ColumnsMd = 6;
})
.WithPage("Network");
}
// Specific form
private void OnConfigureCreate(FastFormConfiguration<Server> configuration, Server server)
{
OnConfigureBase(configuration, server);
configuration.AddProperty(x => x.Node)
.WithComponent<DropdownComponent<ServerNode>>(component =>
{
component.SearchFunc = x => x.Name;
component.DisplayFunc = x => x.Name;
})
.WithValidation(FastFormValidators.Required);
}
private void OnConfigureEdit(FastFormConfiguration<Server> configuration, Server server)
{
OnConfigureBase(configuration, server);
}
private IEnumerable<ServerAllocation> GetAllocation(Server server)
{
if (server == null)
return Array.Empty<ServerAllocation>();
if (server.Node == null)
return Array.Empty<ServerAllocation>();
return server.Allocations.Concat(
AllocRepository
.Get()
.FromSqlRaw($"SELECT * FROM `ServerAllocations` WHERE ServerId IS NULL AND ServerNodeId = {server.Node.Id}"));
}
}