Merge pull request #420 from Moonlight-Panel/v2_ImproveForms
Improved forms with new MoonCore features
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MoonCoreUI.Attributes;
|
||||
|
||||
namespace Moonlight.Core.Models.Forms.Users;
|
||||
|
||||
@@ -16,5 +17,7 @@ public class UpdateUserForm
|
||||
public string Email { get; set; }
|
||||
|
||||
[Description("This toggles the use of the two factor authentication")]
|
||||
[RadioButtonBool("Enabled", "Disabled", TrueIcon = "bx-lock-alt", FalseIcon = "bx-lock-open-alt")]
|
||||
[DisplayName("Two factor authentication")]
|
||||
public bool Totp { get; set; } = false;
|
||||
}
|
||||
@@ -17,30 +17,30 @@
|
||||
<AdminUsersNavigation Index="0"/>
|
||||
|
||||
<AutoCrud TItem="User"
|
||||
TCreateForm="CreateUserForm"
|
||||
TUpdateForm="UpdateUserForm"
|
||||
Title=""
|
||||
Load="Load"
|
||||
CustomAdd="Add"
|
||||
ValidateUpdate="ValidateUpdate">
|
||||
TCreateForm="CreateUserForm"
|
||||
TUpdateForm="UpdateUserForm"
|
||||
Loader="Load"
|
||||
CustomAdd="Add"
|
||||
ValidateUpdate="ValidateUpdate">
|
||||
<View>
|
||||
<CrudColumn TItem="User" Field="@(x => x.Id)" Title="Id" Filterable="true"/>
|
||||
<CrudColumn TItem="User" Field="@(x => x.Email)" Title="Email" Filterable="true"/>
|
||||
<CrudColumn TItem="User" Field="@(x => x.Username)" Title="Username" Filterable="true"/>
|
||||
<CrudColumn TItem="User" Field="@(x => x.CreatedAt)" Title="Created at" />
|
||||
<CrudColumn TItem="User" Field="@(x => x.CreatedAt)" Title="Created at"/>
|
||||
</View>
|
||||
<Actions>
|
||||
<WButton OnClick="() => ChangePassword(context)" CssClasses="btn btn-icon btn-primary">
|
||||
<UpdateActions>
|
||||
<WButton OnClick="() => ChangePassword(context)" CssClasses="btn btn-info me-2">
|
||||
<i class="bx bx-sm bxs-key"></i>
|
||||
Change password
|
||||
</WButton>
|
||||
</Actions>
|
||||
</UpdateActions>
|
||||
</AutoCrud>
|
||||
|
||||
@code
|
||||
{
|
||||
private User[] Load(Repository<User> repository)
|
||||
private IEnumerable<User> Load(Repository<User> repository)
|
||||
{
|
||||
return repository.Get().ToArray();
|
||||
return repository.Get();
|
||||
}
|
||||
|
||||
private async Task ChangePassword(User user)
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
</Tooltip>
|
||||
|
||||
<div class="card mt-5">
|
||||
<div class="card-body">
|
||||
<div class="card-body px-6 py-4">
|
||||
<LazyLoader Load="Load">
|
||||
<CrudTable @ref="Table"
|
||||
TItem="Session"
|
||||
Items="SessionService.Sessions"
|
||||
ItemSource="SessionService.Sessions"
|
||||
PageSize="50">
|
||||
<CrudColumn TItem="Session" Title="User" Field="@(x => x.CreatedAt)">
|
||||
<Template>
|
||||
@@ -63,8 +63,8 @@
|
||||
<Template>
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="btn btn-group">
|
||||
<WButton OnClick="() => Message(context)" Text="Message" CssClasses="btn btn-primary" />
|
||||
<WButton OnClick="() => Redirect(context)" Text="Redirect" CssClasses="btn btn-warning" />
|
||||
<WButton OnClick="() => Message(context)" Text="Message" CssClasses="btn btn-primary"/>
|
||||
<WButton OnClick="() => Redirect(context)" Text="Redirect" CssClasses="btn btn-warning"/>
|
||||
</div>
|
||||
</div>
|
||||
</Template>
|
||||
@@ -85,9 +85,7 @@
|
||||
UpdateTimer = new Timer(async _ =>
|
||||
{
|
||||
if (Table != null)
|
||||
await Table.Refresh();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await Table.Refresh(isSilent: true, fullRefresh: true);
|
||||
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
|
||||
|
||||
return Task.CompletedTask;
|
||||
@@ -97,7 +95,7 @@
|
||||
{
|
||||
var url = await AlertService.Text("Enter the target url to redirect to");
|
||||
|
||||
if(string.IsNullOrEmpty(url))
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return;
|
||||
|
||||
try
|
||||
@@ -116,7 +114,7 @@
|
||||
{
|
||||
var message = await AlertService.Text("Enter the message you want to send");
|
||||
|
||||
if(string.IsNullOrEmpty(message))
|
||||
if (string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
try
|
||||
|
||||
160
Moonlight/Core/UI/Views/Test.razor
Normal file
160
Moonlight/Core/UI/Views/Test.razor
Normal file
@@ -0,0 +1,160 @@
|
||||
@page "/test"
|
||||
|
||||
<div class="card card-body mb-5 p-3">
|
||||
<div class="row">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-sm bx-search-alt"></i>
|
||||
</span>
|
||||
<input placeholder="Search" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-9">
|
||||
<div class="text-end">
|
||||
<div class="btn-group dropstart">
|
||||
<!--!-->
|
||||
<button class="btn btn-icon btn-secondary rounded" type="button" id="defaultDropdown" data-bs-toggle="dropdown" data-bs-auto-close="true" aria-expanded="false">
|
||||
<i class="bx bx-sm bx-filter-alt"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu py-1 px-2 fs-5 me-2 bg-secondary" style="min-width: 15rem">
|
||||
<div class="my-2 text-center">
|
||||
<!--!-->
|
||||
<div class="mt-auto">
|
||||
<input id="Id" type="number" class="form-control ">
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-2 text-center">
|
||||
<!--!-->
|
||||
<div class="mt-auto">
|
||||
<div class="w-100">
|
||||
<input id="Email" type="text" class="form-control " placeholder="Email" autocomplete="off" spellcheck="false">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-2 text-center">
|
||||
<!--!-->
|
||||
<div class="mt-auto">
|
||||
<div class="w-100">
|
||||
<input id="Username" type="text" class="form-control " placeholder="Username" autocomplete="off" spellcheck="false">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-2 text-center">
|
||||
<!--!-->
|
||||
<button class="btn btn btn-primary" type="button">
|
||||
Apply<!--!-->
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary ms-3">
|
||||
<span>Create User</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body pt-3 px-5 pb-4">
|
||||
<div class="table-responsive mb-3">
|
||||
<table class="table table-row-bordered table-row-gray-200 align-middle gs-0 gy-3 fs-6 table-rounded">
|
||||
<!--!--><!--!--><!--!-->
|
||||
<!--!--><!--!-->
|
||||
<!--!--><!--!-->
|
||||
<!--!--><!--!-->
|
||||
|
||||
<!--!--><!--!-->
|
||||
|
||||
<thead class="fw-bold text-muted">
|
||||
<tr>
|
||||
<th class="w-10px align-middle">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="0">
|
||||
</div>
|
||||
</th>
|
||||
<th class="fw-bold">Id</th>
|
||||
<th class="fw-bold">Email</th>
|
||||
<th class="fw-bold">Username</th>
|
||||
<th class="fw-bold">Created at</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead><!--!-->
|
||||
|
||||
<tbody>
|
||||
|
||||
@for (var i = 1; i <= 10; i++)
|
||||
{
|
||||
<tr>
|
||||
<td class="w-10px align-middle">
|
||||
<div class="form-check my-1">
|
||||
<input class="form-check-input" type="checkbox" value="0">
|
||||
</div>
|
||||
</td>
|
||||
<td class="">@(i)</td>
|
||||
<td class="">admin@masuowo.xyz@(i)</td>
|
||||
<td class="">masuowo@(i)</td>
|
||||
<td class="">04/19/2024 12:48:35</td>
|
||||
<td class="py-1 my-0">
|
||||
<div class="text-end text-nowrap me-3 fw-semibold">
|
||||
<a href="#"><i class="bx bx-edit"></i> Edit</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mx-2">
|
||||
<div class="d-none d-lg-block">
|
||||
<span class="text-gray-600 fs-5">Showing 13 of 100 results</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">Per page</span>
|
||||
<select class="form-select">
|
||||
<option selected="selected">10</option>
|
||||
<option>25</option>
|
||||
<option>50</option>
|
||||
<option>100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<ul class="pagination">
|
||||
<li class="page-item previous disabled"><a href="#" class="page-link"><i class="previous"></i></a></li>
|
||||
<li class="page-item "><a href="#" class="page-link">1</a></li>
|
||||
<li class="page-item active"><a href="#" class="page-link">2</a></li>
|
||||
<li class="page-item "><a href="#" class="page-link">3</a></li>
|
||||
<li class="page-item next"><a href="#" class="page-link"><i class="next"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@*
|
||||
<div class="my-5"></div>
|
||||
|
||||
<div class="input-group mb-5">
|
||||
<input type="text" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="input-group has-validation mb-5">
|
||||
<input type="text" class="form-control is-invalid">
|
||||
<div class="invalid-feedback">
|
||||
Please choose a username.
|
||||
</div>
|
||||
</div>*@
|
||||
|
||||
<WButton Text="Do something" CssClasses="btn-primary" OnClick="Do"></WButton>
|
||||
|
||||
@code
|
||||
{
|
||||
private async Task Do()
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
110
Moonlight/Core/UI/Views/Testy.razor
Normal file
110
Moonlight/Core/UI/Views/Testy.razor
Normal file
@@ -0,0 +1,110 @@
|
||||
@page "/testy"
|
||||
|
||||
<div class="card card-body mb-8">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="#" class="text-body fs-4">
|
||||
<i class="bx bx-md bx-chevron-left align-middle"></i>
|
||||
<span class="align-middle">Back</span>
|
||||
</a>
|
||||
|
||||
<button class="btn btn-primary">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-6">
|
||||
<div class="card-body">
|
||||
<div class="row g-8">
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Default input</label>
|
||||
<input type="text" class="form-control" placeholder="name@example.com"/>
|
||||
<div class="form-text fs-6 text-gray-600">
|
||||
dasdfuoiusefiousidfhzuigfzduzfgiudzfogzidufzgduifzguisdzugzduifhgjkdsgkdfh
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-3">
|
||||
<label class="form-label">Default input</label>
|
||||
<input type="number" class="form-control" value="10324"/>
|
||||
<div class="form-text fs-6 text-gray-600">
|
||||
dasdfuoiusefiousidfhzuigfzduzfgiudzfogzidufzgduifzguisdzugzduifhgjkdsgkdfh
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-12">
|
||||
<label class="form-label">Default input</label>
|
||||
<textarea class="form-control"></textarea>
|
||||
<div class="form-text fs-6 text-gray-600">
|
||||
dasdfuoiusefiousidfhzuigfzduzfgiudzfogzidufzgduifzguisdzugzduifhgjkdsgkdfh
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-3">
|
||||
<label class="form-label">Default input</label>
|
||||
<div class="btn-group w-100">
|
||||
<label class="btn btn-outline btn-color-muted btn-active-primary">
|
||||
<input class="btn-check" type="radio" name="method" value="1" />
|
||||
<i class="bx bx-sm bx-lock-open-alt"></i>
|
||||
HTTP
|
||||
</label>
|
||||
|
||||
<label class="btn btn-outline btn-color-muted btn-active-primary">
|
||||
<input class="btn-check" type="radio" name="method" value="1" />
|
||||
<i class="bx bx-sm bx-lock-alt"></i>
|
||||
HTTPS
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text fs-6 text-gray-600">
|
||||
dasdfuoiusefiousidfhzuigfzduzfgiudzfogzidufzgduifzguisdzugzduifhgjkdsgkdfh
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<i class="bx bx-md bx-server text-primary me-3 align-middle"></i>
|
||||
<span class="h3 align-middle">Some Section</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-8">
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Default input</label>
|
||||
<input type="text" class="form-control" placeholder="name@example.com"/>
|
||||
<div class="form-text fs-6 text-gray-600">
|
||||
dasdfuoiusefiousidfhzuigfzduzfgiudzfogzidufzgduifzguisdzugzduifhgjkdsgkdfh
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-3">
|
||||
<label class="form-label">Default input</label>
|
||||
<input type="number" class="form-control" value="10324"/>
|
||||
<div class="form-text fs-6 text-gray-600">
|
||||
dasdfuoiusefiousidfhzuigfzduzfgiudzfogzidufzgduifzguisdzugzduifhgjkdsgkdfh
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-3">
|
||||
<label class="form-label">Default input</label>
|
||||
<div class="btn-group w-100">
|
||||
<label class="btn btn-outline btn-color-muted btn-active-primary">
|
||||
<input class="btn-check" type="radio" name="method" value="1"/>
|
||||
<i class="bx bx-sm bx-lock-open-alt"></i>
|
||||
HTTP
|
||||
</label>
|
||||
|
||||
<label class="btn btn-outline btn-color-muted btn-active-primary">
|
||||
<input class="btn-check" type="radio" name="method" value="1"/>
|
||||
<i class="bx bx-sm bx-lock-alt"></i>
|
||||
HTTPS
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text fs-6 text-gray-600">
|
||||
dasdfuoiusefiousidfhzuigfzduzfgiudzfogzidufzgduifzguisdzugzduifhgjkdsgkdfh
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -21,27 +21,35 @@ public class CreateServerForm
|
||||
|
||||
[Range(1, int.MaxValue, ErrorMessage = "Enter a valid cpu value")]
|
||||
[Description("The cores the server will be able to use. 100 = 1 Core")]
|
||||
[Section("Resources")]
|
||||
[Section("Resources", Icon = "bxs-chip")]
|
||||
public int Cpu { get; set; }
|
||||
|
||||
[Range(1, int.MaxValue, ErrorMessage = "Enter a valid memory value")]
|
||||
[Description("The amount of memory this server will be able to use")]
|
||||
[ByteSize(MinimumUnit = 1, Converter = 1, DefaultUnit = 2)]
|
||||
[Section("Resources")]
|
||||
[Section("Resources", Icon = "bxs-chip")]
|
||||
public int Memory { get; set; }
|
||||
|
||||
[Range(1, int.MaxValue, ErrorMessage = "Enter a valid disk value")]
|
||||
[Description("The amount of disk space this server will be able to use")]
|
||||
[ByteSize(MinimumUnit = 1, Converter = 1, DefaultUnit = 2)]
|
||||
[Section("Resources")]
|
||||
[Section("Resources", Icon = "bxs-chip")]
|
||||
public int Disk { get; set; }
|
||||
|
||||
[Description("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")]
|
||||
[Section("Deployment")]
|
||||
[Section("Deployment", Icon = "bx-cube")]
|
||||
[RadioButtonBool("Virtual Disk", "Simple Volume", TrueIcon = "bxs-hdd", FalseIcon = "bxs-data")]
|
||||
[DisplayName("Storage")]
|
||||
public bool UseVirtualDisk { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "You need to specify a server node")]
|
||||
[Selector(SelectorProp = "Name", DisplayProp = "Name", UseDropdown = true)]
|
||||
[Section("Deployment")]
|
||||
[Section("Deployment", Icon = "bx-cube")]
|
||||
public ServerNode Node { get; set; }
|
||||
|
||||
[Description("The allocations the server should have")]
|
||||
[MultiSelection("Port", "Port", Icon = "bx-network-chart")]
|
||||
[Section("Deployment", Icon = "bx-cube")]
|
||||
[CustomItemLoader("FreeAllocations")]
|
||||
public List<ServerAllocation> Allocations { get; set; } = new();
|
||||
}
|
||||
@@ -94,23 +94,53 @@ public class ServerService
|
||||
var user = userRepo.Get().First(x => x.Id == form.Owner.Id);
|
||||
|
||||
// Load and validate server allocations
|
||||
ServerAllocation[] allocations = Array.Empty<ServerAllocation>();
|
||||
List<ServerAllocation> allocations = new();
|
||||
|
||||
if (false)
|
||||
var amountOfAutoAllocations = image.AllocationsNeeded;
|
||||
|
||||
if (form.Allocations.Count > 0)
|
||||
{
|
||||
throw new DisplayException(
|
||||
"The dedicated ip mode has not been implemented yet. Please disable the dedicated ip option in the product configuration");
|
||||
}
|
||||
else
|
||||
{
|
||||
allocations = allocationRepo
|
||||
.Get()
|
||||
.FromSqlRaw(
|
||||
$"SELECT * FROM `ServerAllocations` WHERE ServerId IS NULL AND ServerNodeId={node.Id} LIMIT {image.AllocationsNeeded}")
|
||||
.ToArray();
|
||||
amountOfAutoAllocations -= form.Allocations.Count;
|
||||
|
||||
foreach (var serverAllocation in form.Allocations) // Resolve all allocations specified in the form in the current scope
|
||||
{
|
||||
var allocationInCurrentScope = allocationRepo.Get().First(x => x.Id == serverAllocation.Id);
|
||||
allocations.Add(allocationInCurrentScope);
|
||||
}
|
||||
}
|
||||
|
||||
if (allocations.Length < 1 || allocations.Length < image.AllocationsNeeded)
|
||||
if (amountOfAutoAllocations > 0) // Resolve all other allocations which are required automatically
|
||||
{
|
||||
if (false)
|
||||
{
|
||||
throw new DisplayException(
|
||||
"The dedicated ip mode has not been implemented yet. Please disable the dedicated ip option in the product configuration");
|
||||
}
|
||||
else
|
||||
{
|
||||
var autoAllocations = allocationRepo
|
||||
.Get()
|
||||
.FromSqlRaw(
|
||||
$"SELECT * FROM `ServerAllocations` WHERE ServerId IS NULL AND ServerNodeId={node.Id} LIMIT {image.AllocationsNeeded + form.Allocations.Count}")
|
||||
.ToArray();
|
||||
|
||||
var addedAutoAllocations = 0;
|
||||
|
||||
foreach (var autoAllocation in autoAllocations)
|
||||
{
|
||||
if(addedAutoAllocations >= amountOfAutoAllocations)
|
||||
break;
|
||||
|
||||
if(form.Allocations.Any(x => x.Id == autoAllocation.Id))
|
||||
continue;
|
||||
|
||||
allocations.Add(autoAllocation);
|
||||
addedAutoAllocations++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allocations.Count < 1 || allocations.Count < image.AllocationsNeeded)
|
||||
throw new DisplayException($"Not enough free allocations found on node '{node.Name}'");
|
||||
|
||||
// Build server db model
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="card card-body mb-15 py-3 px-5">
|
||||
@if (!Server.DisablePublicNetwork)
|
||||
{
|
||||
<CrudTable TItem="ServerAllocation" Items="Server.Allocations" PageSize="25" ShowPagination="false">
|
||||
<CrudTable TItem="ServerAllocation" ItemSource="Server.Allocations" PageSize="25" ShowPagination="false">
|
||||
<CrudColumn TItem="ServerAllocation" Field="@(x => x.IpAddress)" Title="FQDN or dedicated IP">
|
||||
<Template>
|
||||
@if (context!.IpAddress == "0.0.0.0")
|
||||
|
||||
@@ -28,43 +28,43 @@
|
||||
<AutoCrud TItem="ServerImage"
|
||||
TCreateForm="CreateImageForm"
|
||||
TUpdateForm="UpdateImageForm"
|
||||
Title=""
|
||||
Load="Load"
|
||||
Loader="Load"
|
||||
ValidateDelete="ValidateDelete"
|
||||
ValidateAdd="ValidateAdd"
|
||||
CustomDelete="CustomDelete"
|
||||
@ref="Crud">
|
||||
<Actions>
|
||||
<a href="/admin/servers/images/view/@(context.Id)" class="btn btn-icon btn-info">
|
||||
<i class="bx bx-sm bx-wrench"></i>
|
||||
</a>
|
||||
</Actions>
|
||||
<View>
|
||||
<CrudColumn TItem="ServerImage" Field="@(x => x.Id)" Title="Id" Filterable="true"/>
|
||||
<CrudColumn TItem="ServerImage" Field="@(x => x.Name)" Title="Name" Filterable="true"/>
|
||||
<CrudColumn TItem="ServerImage" Field="@(x => x.Name)" Title="Name" Filterable="true">
|
||||
<Template>
|
||||
<a href="/admin/servers/images/view/@context!.Id">@context.Name</a>
|
||||
</Template>
|
||||
</CrudColumn>
|
||||
<CrudColumn TItem="ServerImage" Field="@(x => x.Author)" Title="Author" Filterable="true"/>
|
||||
<CrudColumn TItem="ServerImage">
|
||||
<Template>
|
||||
@if (!string.IsNullOrEmpty(context.UpdateUrl))
|
||||
{
|
||||
<WButton CssClasses="btn btn-sm btn-info me-3">
|
||||
<i class="bx bx-refresh"></i>
|
||||
Update
|
||||
</WButton>
|
||||
}
|
||||
<div class="text-end">
|
||||
@if (!string.IsNullOrEmpty(context.UpdateUrl))
|
||||
{
|
||||
<a class="me-2" href="#" @onclick:preventDefault>
|
||||
<i class="bx bx-refresh"></i>
|
||||
Update
|
||||
</a>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(context.DonateUrl))
|
||||
{
|
||||
<a class="btn btn-sm btn-primary me-3" href="@(context.DonateUrl)" target="_blank">
|
||||
<i class="bx bxs-heart text-danger"></i>
|
||||
Donate
|
||||
@if (!string.IsNullOrEmpty(context.DonateUrl))
|
||||
{
|
||||
<a class="me-2" href="@(context.DonateUrl)" target="_blank">
|
||||
<i class="bx bxs-heart text-danger"></i>
|
||||
Donate
|
||||
</a>
|
||||
}
|
||||
|
||||
<a href="#" class="me-2" @onclick:preventDefault @onclick="() => Export(context)">
|
||||
<i class="bx bx-download"></i>
|
||||
Export
|
||||
</a>
|
||||
}
|
||||
|
||||
<WButton OnClick="() => Export(context)" CssClasses="btn btn-sm btn-warning">
|
||||
<i class="bx bx-download"></i>
|
||||
Export
|
||||
</WButton>
|
||||
</div>
|
||||
</Template>
|
||||
</CrudColumn>
|
||||
</View>
|
||||
@@ -73,7 +73,7 @@
|
||||
Download and import a image from our <a href="https://github.com/Moonlight-Panel/Images">repository</a> or create a new one. Need help? Check out our <a href="https://docs.moonlightpanel.xyz">documentation</a>
|
||||
</IconAlert>
|
||||
</NoItemsView>
|
||||
<Toolbar>
|
||||
<OverviewToolbar>
|
||||
<SmartCustomFileSelect @ref="EggUpload" OnFileSelected="ImportEgg">
|
||||
<a class="btn btn-info me-3">
|
||||
Import egg
|
||||
@@ -84,7 +84,7 @@
|
||||
Import
|
||||
</a>
|
||||
</SmartCustomFileSelect>
|
||||
</Toolbar>
|
||||
</OverviewToolbar>
|
||||
</AutoCrud>
|
||||
|
||||
@code
|
||||
@@ -93,9 +93,9 @@
|
||||
private SmartCustomFileSelect ImageUpload;
|
||||
private SmartCustomFileSelect EggUpload;
|
||||
|
||||
private ServerImage[] Load(Repository<ServerImage> repository)
|
||||
private IEnumerable<ServerImage> Load(Repository<ServerImage> repository)
|
||||
{
|
||||
return repository.Get().ToArray();
|
||||
return repository.Get();
|
||||
}
|
||||
|
||||
private Task ValidateDelete(ServerImage serverImage)
|
||||
|
||||
@@ -4,12 +4,16 @@
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using MoonCore.Abstractions
|
||||
@using MoonCore.Exceptions
|
||||
@using MoonCore.Helpers
|
||||
@using MoonCoreUI.Services
|
||||
@using Moonlight.Features.Servers.Entities
|
||||
@using Moonlight.Features.Servers.Models.Enums
|
||||
@using Moonlight.Features.Servers.Models.Forms.Admin.Servers
|
||||
@using Moonlight.Features.Servers.Services
|
||||
|
||||
@inject ServerService ServerService
|
||||
@inject Repository<Server> ServerRepository
|
||||
@inject ToastService ToastService
|
||||
|
||||
@attribute [RequirePermission(5000)]
|
||||
|
||||
@@ -18,9 +22,9 @@
|
||||
<AutoCrud TItem="Server"
|
||||
TCreateForm="CreateServerForm"
|
||||
TUpdateForm="CreateServerForm"
|
||||
Title=""
|
||||
Load="Load"
|
||||
Loader="Load"
|
||||
CustomAdd="CustomAdd"
|
||||
CustomUpdate="CustomUpdate"
|
||||
ValidateUpdate="ValidateUpdate"
|
||||
CustomDelete="CustomDelete">
|
||||
<View>
|
||||
@@ -42,6 +46,9 @@
|
||||
</Template>
|
||||
</CrudColumn>
|
||||
</View>
|
||||
<CustomLoaders>
|
||||
<DefineCustomLoader TItem="Server" T="ServerAllocation" Id="FreeAllocations" Func="LoadFreeAllocations"/>
|
||||
</CustomLoaders>
|
||||
<NoItemsView>
|
||||
<IconAlert Title="No servers found" Color="primary" Icon="bx-search-alt">
|
||||
Create a new server in order to manage it using this page. Need help? Check out our <a href="https://docs.moonlightpanel.xyz">documentation</a>
|
||||
@@ -51,29 +58,85 @@
|
||||
|
||||
@code
|
||||
{
|
||||
private Server[] Load(Repository<Server> repository)
|
||||
private IEnumerable<Server> Load(Repository<Server> repository)
|
||||
{
|
||||
return repository
|
||||
.Get()
|
||||
.Include(x => x.Owner)
|
||||
.Include(x => x.Image)
|
||||
.Include(x => x.Node)
|
||||
.ToArray();
|
||||
.Include(x => x.Allocations)
|
||||
.Include(x => x.Node);
|
||||
}
|
||||
|
||||
private IEnumerable<ServerAllocation> LoadFreeAllocations(Repository<ServerAllocation> repository, Server currentServer)
|
||||
{
|
||||
return currentServer.Allocations.Concat(
|
||||
repository
|
||||
.Get()
|
||||
.FromSqlRaw($"SELECT * FROM `ServerAllocations` WHERE ServerId IS NULL AND ServerNodeId = {currentServer.Node.Id}")
|
||||
.AsEnumerable() // => executes the sql
|
||||
);
|
||||
}
|
||||
|
||||
private async Task CustomAdd(Server form) => await ServerService.Create(form);
|
||||
|
||||
private async Task CustomDelete(Server s) => await ServerService.Delete(s);
|
||||
|
||||
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.Error("Unable to sync server changes due to an error occuring");
|
||||
Logger.Error(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;
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,7 @@
|
||||
<AutoCrud TItem="ServerNode"
|
||||
TCreateForm="CreateNodeForm"
|
||||
TUpdateForm="UpdateNodeForm"
|
||||
Title=""
|
||||
Load="LoadData"
|
||||
Loader="LoadData"
|
||||
ValidateAdd="ValidateAdd"
|
||||
ValidateUpdate="ValidateUpdate"
|
||||
ValidateDelete="ValidateDelete">
|
||||
@@ -154,11 +153,10 @@
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private ServerNode[] LoadData(Repository<ServerNode> repository)
|
||||
private IEnumerable<ServerNode> LoadData(Repository<ServerNode> repository)
|
||||
{
|
||||
return repository
|
||||
.Get()
|
||||
.ToArray();
|
||||
.Get();
|
||||
}
|
||||
|
||||
private Task ValidateDelete(ServerNode node)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<AutoCrud TItem="ServerNetwork"
|
||||
TCreateForm="CreateNetworkForm"
|
||||
TUpdateForm="UpdateNetworkForm"
|
||||
Load="Load"
|
||||
Loader="Load"
|
||||
ValidateAdd="ValidateAdd">
|
||||
<View>
|
||||
<CrudColumn TItem="ServerNetwork" Field="@(x => x.Name)" Title="Name"/>
|
||||
@@ -28,7 +28,7 @@
|
||||
<CrudColumn TItem="ServerNetwork" Title="Used by">
|
||||
<Template>
|
||||
@{
|
||||
var servers = UsedByCache[context.Id];
|
||||
var servers = UsedByCache.ContainsKey(context.Id) ? UsedByCache[context.Id] : Array.Empty<Server>();
|
||||
}
|
||||
|
||||
<span>
|
||||
@@ -51,13 +51,12 @@
|
||||
{
|
||||
private readonly Dictionary<int, Server[]> UsedByCache = new();
|
||||
|
||||
private ServerNetwork[] Load(Repository<ServerNetwork> repository)
|
||||
private IEnumerable<ServerNetwork> Load(Repository<ServerNetwork> repository)
|
||||
{
|
||||
var result = repository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Where(x => x.User.Id == IdentityService.CurrentUser.Id)
|
||||
.ToArray();
|
||||
.Where(x => x.User.Id == IdentityService.CurrentUser.Id);
|
||||
|
||||
UsedByCache.Clear();
|
||||
|
||||
|
||||
@@ -91,8 +91,8 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MoonCore" Version="1.3.3" />
|
||||
<PackageReference Include="MoonCoreUI" Version="1.1.7" />
|
||||
<PackageReference Include="MoonCore" Version="1.3.4" />
|
||||
<PackageReference Include="MoonCoreUI" Version="1.1.8" />
|
||||
<PackageReference Include="Otp.NET" Version="1.3.0" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<PackageReference Include="XtermBlazor" Version="1.10.2" />
|
||||
|
||||
Reference in New Issue
Block a user