Added node allocations ui and crud controller

Multi actions are not done though
This commit is contained in:
2024-12-13 22:33:29 +01:00
parent edc91229e1
commit 680827e0ea
12 changed files with 434 additions and 10 deletions

View File

@@ -0,0 +1,93 @@
using Microsoft.AspNetCore.Mvc;
using MoonCore.Attributes;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers;
using MoonCore.Helpers;
using MoonCore.Models;
using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations;
using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations;
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
[ApiController]
[Route("api/admin/servers/nodes")]
public class NodeAllocationsController : Controller
{
private readonly CrudHelper<Allocation, NodeAllocationDetailResponse> CrudHelper;
private readonly DatabaseRepository<Node> NodeRepository;
private readonly DatabaseRepository<Allocation> AllocationRepository;
private Node Node;
public NodeAllocationsController(CrudHelper<Allocation, NodeAllocationDetailResponse> crudHelper, DatabaseRepository<Node> nodeRepository, DatabaseRepository<Allocation> allocationRepository)
{
CrudHelper = crudHelper;
NodeRepository = nodeRepository;
AllocationRepository = allocationRepository;
}
private void ApplyNode(int id)
{
var node = NodeRepository
.Get()
.FirstOrDefault(x => x.Id == id);
if (node == null)
throw new HttpApiException("A node with this id could not be found", 404);
Node = node;
CrudHelper.QueryModifier = variables =>
variables.Where(x => x.Node.Id == node.Id);
}
[HttpGet("{nodeId:int}/allocations")]
[RequirePermission("admin.servers.nodes.get")]
public async Task<IPagedData<NodeAllocationDetailResponse>> Get([FromRoute] int nodeId, [FromQuery] int page, [FromQuery] int pageSize)
{
ApplyNode(nodeId);
return await CrudHelper.Get(page, pageSize);
}
[HttpGet("{nodeId:int}/allocations/{id:int}")]
[RequirePermission("admin.servers.nodes.get")]
public async Task<NodeAllocationDetailResponse> GetSingle([FromRoute] int nodeId, [FromRoute] int id)
{
ApplyNode(nodeId);
return await CrudHelper.GetSingle(id);
}
[HttpPost("{nodeId:int}/allocations")]
[RequirePermission("admin.servers.nodes.create")]
public async Task<NodeAllocationDetailResponse> Create([FromRoute] int nodeId, [FromBody] CreateNodeAllocationRequest request)
{
ApplyNode(nodeId);
var allocation = Mapper.Map<Allocation>(request);
allocation.Node = Node;
var finalVariable = AllocationRepository.Add(allocation);
return CrudHelper.MapToResult(finalVariable);
}
[HttpPatch("{nodeId:int}/allocations/{id:int}")]
public async Task<NodeAllocationDetailResponse> Update([FromRoute] int nodeId, [FromRoute] int id, [FromBody] UpdateNodeAllocationRequest request)
{
ApplyNode(nodeId);
return await CrudHelper.Update(id, request);
}
[HttpDelete("{nodeId:int}/allocations/{id:int}")]
public async Task Delete([FromRoute] int nodeId, [FromRoute] int id)
{
ApplyNode(nodeId);
await CrudHelper.Delete(id);
}
}

View File

@@ -0,0 +1,50 @@
@using MoonCore.Blazor.Tailwind.Modals.Components
@using MoonCore.Blazor.Tailwind.Components
@using MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations
@inherits BaseModal
<h1 class="mb-5 font-semibold text-xl">Add a new allocation</h1>
<HandleForm @ref="HandleForm" Model="Form" OnValidSubmit="OnValidSubmit">
<div class="grid grid-cols-1 gap-2">
<div class="col-span-1">
<label class="block text-sm font-medium leading-6 text-white">IP Address</label>
<input @bind="Form.IpAddress" type="text" class="form-input w-full"/>
</div>
<div class="col-span-1">
<label class="block text-sm font-medium leading-6 text-white">Port</label>
<input @bind="Form.Port" type="text" class="form-input w-full"/>
</div>
</div>
</HandleForm>
<div class="mt-5 flex space-x-2">
<WButton OnClick="_ => Hide()" CssClasses="btn btn-secondary grow">Cancel</WButton>
<WButton OnClick="_ => Submit()" CssClasses="btn btn-primary grow">Create</WButton>
</div>
@code
{
[Parameter] public Func<CreateNodeAllocationRequest, Task> OnSubmit { get; set; }
private CreateNodeAllocationRequest Form;
private HandleForm HandleForm;
protected override void OnInitialized()
{
Form = new()
{
IpAddress = "0.0.0.0"
};
}
private async Task OnValidSubmit()
{
await OnSubmit.Invoke(Form);
await Hide();
}
private Task Submit() => HandleForm.Submit();
}

View File

@@ -0,0 +1,50 @@
@using MoonCore.Blazor.Tailwind.Modals.Components
@using MoonCore.Blazor.Tailwind.Components
@using MoonCore.Helpers
@using MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations
@using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations
@inherits BaseModal
<h1 class="mb-5 font-semibold text-xl">Update allocation</h1>
<HandleForm @ref="HandleForm" Model="Form" OnValidSubmit="OnValidSubmit">
<div class="grid grid-cols-1 gap-2">
<div class="col-span-1">
<label class="block text-sm font-medium leading-6 text-white">IP Address</label>
<input @bind="Form.IpAddress" type="text" class="form-input w-full"/>
</div>
<div class="col-span-1">
<label class="block text-sm font-medium leading-6 text-white">Port</label>
<input @bind="Form.Port" type="text" class="form-input w-full"/>
</div>
</div>
</HandleForm>
<div class="mt-5 flex space-x-2">
<WButton OnClick="_ => Hide()" CssClasses="btn btn-secondary grow">Cancel</WButton>
<WButton OnClick="_ => Submit()" CssClasses="btn btn-primary grow">Update</WButton>
</div>
@code
{
[Parameter] public Func<UpdateNodeAllocationRequest, Task> OnSubmit { get; set; }
[Parameter] public NodeAllocationDetailResponse Allocation { get; set; }
private UpdateNodeAllocationRequest Form;
private HandleForm HandleForm;
protected override void OnInitialized()
{
Form = Mapper.Map<UpdateNodeAllocationRequest>(Allocation);
}
private async Task OnValidSubmit()
{
await OnSubmit.Invoke(Form);
await Hide();
}
private Task Submit() => HandleForm.Submit();
}

View File

@@ -0,0 +1,26 @@
@using MoonlightServers.Frontend.UI.Components.Forms
@using MoonlightServers.Shared.Http.Requests.Admin.Nodes
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div class="sm:col-span-2"><label class="block text-sm font-medium leading-6 text-white">Enable Transparent
Mode</label>
<div class="mt-2">
<div class="flex items-center">
<Switch @bind-Value="Request.EnableTransparentMode" />
</div>
</div>
</div>
<div class="sm:col-span-2"><label class="block text-sm font-medium leading-6 text-white">Enable Dynamic
Firewall</label>
<div class="mt-2">
<div class="flex items-center">
<Switch @bind-Value="Request.EnableDynamicFirewall" />
</div>
</div>
</div>
</div>
@code
{
[Parameter] public UpdateNodeRequest Request { get; set; }
}

View File

@@ -0,0 +1,117 @@
@using MoonCore.Blazor.Tailwind.Alerts
@using MoonlightServers.Shared.Http.Responses.Admin.Nodes
@using MoonCore.Blazor.Tailwind.DataTable
@using MoonCore.Blazor.Tailwind.Modals
@using MoonCore.Blazor.Tailwind.Toasts
@using MoonCore.Helpers
@using MoonCore.Models
@using MoonlightServers.Frontend.UI.Components.Nodes.Modals
@using MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations
@using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations
@inject HttpApiClient ApiClient
@inject ModalService ModalService
@inject ToastService ToastService
@inject AlertService AlertService
<div class="grid grid-cols-1 md:grid-cols-3 md:gap-x-5">
<div class="col-span-1">
<div class="card">
<div class="card-header card-header bg-gray-700 rounded-t-lg text-gray-500 bg-opacity-50 border-0 py-2 text-base font-semibold">
<span class="card-title">Actions</span>
</div>
<div class="card-body">
<div class="flex flex-col gap-y-3">
<button @onclick="AddAllocation" class="btn btn-primary">Create allocation</button>
<button class="btn btn-tertiary">Add multiple</button>
<button class="btn btn-danger">Delete all</button>
</div>
</div>
</div>
</div>
<div class="col-span-1 md:col-span-2 -mb-3">
<ItemDataTable @ref="Table"
TItem="NodeAllocationDetailResponse"
Title=""
ItemLoader="Load">
<ChildContent>
<DataColumn TItem="NodeAllocationDetailResponse" Field="@(x => x.IpAddress)" Title="IP Address"/>
<DataColumn TItem="NodeAllocationDetailResponse" Field="@(x => x.Port)" Title="Port"/>
<DataColumn TItem="NodeAllocationDetailResponse" Title="">
<Template>
<div class="flex justify-end items-center">
<a @onclick="() => UpdateAllocation(context)" @onclick:preventDefault href="#" class="text-primary-500 mr-2 sm:mr-3">
<i class="icon-pencil text-base"></i>
</a>
<a @onclick="() => DeleteAllocation(context)" @onclick:preventDefault href="#" class="text-danger-500">
<i class="icon-trash text-base"></i>
</a>
</div>
</Template>
</DataColumn>
</ChildContent>
</ItemDataTable>
</div>
</div>
@code
{
[Parameter] public NodeDetailResponse Node { get; set; }
private ItemDataTable<NodeAllocationDetailResponse> Table;
private async Task<IPagedData<NodeAllocationDetailResponse>> Load(int page, int pageSize)
{
return await ApiClient.GetJson<PagedData<NodeAllocationDetailResponse>>(
$"api/admin/servers/nodes/{Node.Id}/allocations?page={page}&pageSize={pageSize}"
);
}
private async Task AddAllocation()
{
Func<CreateNodeAllocationRequest, Task> onSubmit = async request =>
{
await ApiClient.Post($"api/admin/servers/nodes/{Node.Id}/allocations", request);
await ToastService.Success("Successfully created allocation");
await Table.Refresh(isSilent: false, bypassCache: true);
};
await ModalService.Launch<CreateAllocationModal>(parameters =>
{
parameters.Add("OnSubmit", onSubmit);
});
}
private async Task UpdateAllocation(NodeAllocationDetailResponse allocation)
{
Func<UpdateNodeAllocationRequest, Task> onSubmit = async request =>
{
await ApiClient.Patch($"api/admin/servers/nodes/{Node.Id}/allocations/{allocation.Id}", request);
await ToastService.Success("Successfully updated allocation");
await Table.Refresh(isSilent: false, bypassCache: true);
};
await ModalService.Launch<UpdateAllocationModal>(parameters =>
{
parameters.Add("OnSubmit", onSubmit);
parameters.Add("Allocation", allocation);
});
}
private async Task DeleteAllocation(NodeAllocationDetailResponse allocation)
{
await AlertService.ConfirmDanger(
"Delete allocation",
"Do you really want to delete the selected allocation? This cannot be undone",
async () =>
{
await ApiClient.Delete($"api/admin/servers/nodes/{Node.Id}/allocations/{allocation.Id}");
await ToastService.Success("Successfully deleted allocation");
await Table.Refresh(isSilent: false, bypassCache: true);
}
);
}
}

View File

@@ -0,0 +1,32 @@
@using MoonlightServers.Shared.Http.Requests.Admin.Nodes
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div class="sm:col-span-2">
<label class="block text-sm font-medium leading-6 text-white">Name</label>
<div class="mt-2">
<input @bind="Request.Name" type="text" autocomplete="off" class="form-input w-full">
</div>
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium leading-6 text-white">Fqdn</label>
<div class="mt-2">
<input @bind="Request.Fqdn" type="text" autocomplete="off" class="form-input w-full">
</div>
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium leading-6 text-white">Http Port</label>
<div class="mt-2">
<input @bind="Request.HttpPort" type="number" autocomplete="off" class="form-input w-full">
</div>
</div>
<div class="sm:col-span-2"><label class="block text-sm font-medium leading-6 text-white">Ftp Port</label>
<div class="mt-2">
<input @bind="Request.FtpPort" type="number" autocomplete="off" class="form-input w-full">
</div>
</div>
</div>
@code
{
[Parameter] public UpdateNodeRequest Request { get; set; }
}

View File

@@ -22,7 +22,15 @@
<MinimalCrud TItem="NodeDetailResponse" OnConfigure="OnConfigure">
<ChildContent>
<DataColumn TItem="NodeDetailResponse" Field="@(x => x.Id)" Title="Id" IsSortable="true"/>
<DataColumn TItem="NodeDetailResponse" Field="@(x => x.Name)" Title="Name" IsSortable="true"/>
<DataColumn TItem="NodeDetailResponse" Field="@(x => x.Name)" Title="Name" IsSortable="true">
<Template>
@{
var url = ComponentHelper.GetRouteOfComponent<Update>(context.Id)!;
}
<a class="text-primary-500" href="@url">@context.Name</a>
</Template>
</DataColumn>
<DataColumn TItem="NodeDetailResponse" Field="@(x => x.Fqdn)" Title="Fqdn"/>
<DataColumn TItem="NodeDetailResponse" Field="@(x => x.Fqdn)" Title="Status">
<Template>

View File

@@ -6,6 +6,7 @@
@using MoonCore.Helpers
@using MoonlightServers.Shared.Http.Requests.Admin.Nodes
@using MoonlightServers.Shared.Http.Responses.Admin.Nodes
@using MoonlightServers.Frontend.UI.Components.Nodes.UpdateNodePartials
@inject HttpApiClient ApiClient
@inject NavigationManager Navigation
@@ -25,7 +26,21 @@
<div class="mt-5">
<HandleForm @ref="Form" Model="Request" OnValidSubmit="OnSubmit">
<GeneratedForm TForm="UpdateNodeRequest" Model="Request" OnConfigure="OnConfigure"/>
<Tabs>
<Tab Name="Settings">
<GeneralNodeUpdate Request="Request"/>
</Tab>
<Tab Name="Allocations">
<AllocationsNodeUpdate Node="Node" />
</Tab>
<Tab Name="Advanced Settings">
<AdvancedNodeUpdate Request="Request"/>
</Tab>
</Tabs>
</HandleForm>
</div>
</LazyLoader>
@@ -36,11 +51,12 @@
private HandleForm Form;
private UpdateNodeRequest Request;
private NodeDetailResponse Node;
private async Task Load(LazyLoader _)
{
var detail = await ApiClient.GetJson<NodeDetailResponse>($"api/admin/servers/nodes/{Id}");
Request = Mapper.Map<UpdateNodeRequest>(detail);
Node = await ApiClient.GetJson<NodeDetailResponse>($"api/admin/servers/nodes/{Id}");
Request = Mapper.Map<UpdateNodeRequest>(Node);
}
private void OnConfigure(FormConfiguration<UpdateNodeRequest> configuration)

View File

@@ -21,7 +21,15 @@
<MinimalCrud @ref="Crud" TItem="StarDetailResponse" OnConfigure="OnConfigure">
<ChildContent>
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Id)" Title="Id" IsSortable="true" />
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Name)" Title="Name" IsSortable="true"/>
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Name)" Title="Name" IsSortable="true">
<Template>
@{
var url = ComponentHelper.GetRouteOfComponent<Update>(context.Id)!;
}
<a class="text-primary-500" href="@url">@context.Name</a>
</Template>
</DataColumn>
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Version)" Title="Version" IsSortable="true"/>
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Author)" Title="Author"/>
</ChildContent>

View File

@@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
namespace MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations;
public class CreateNodeAllocationRequest
{
[Required(ErrorMessage = "You need to provide an ip address")]
[RegularExpression(@"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$", ErrorMessage = "You need to provide a valid ip address")]
public string IpAddress { get; set; }
[Required(ErrorMessage = "You need to provide a port")]
[Range(1, 65535, ErrorMessage = "You need to provide a valid port")]
public int Port { get; set; }
}

View File

@@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
namespace MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations;
public class UpdateNodeAllocationRequest
{
[Required(ErrorMessage = "You need to provide an ip address")]
[RegularExpression(@"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$", ErrorMessage = "You need to provide a valid ip address")]
public string IpAddress { get; set; }
[Required(ErrorMessage = "You need to provide a port")]
[Range(1, 65535, ErrorMessage = "You need to provide a valid port")]
public int Port { get; set; }
}

View File

@@ -6,8 +6,4 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="Http\Requests\Admin\NodeAllocations\" />
</ItemGroup>
</Project>