Files
Servers/MoonlightServers.Client/UI/Views/Admin/Nodes.razor

211 lines
8.0 KiB
Plaintext

@page "/admin/servers/nodes"
@using Moonlight.Client.App.Models.Crud
@using Moonlight.Shared.Http.Resources
@using MoonlightServers.Client.UI.Components.Forms
@using MoonlightServers.Shared.Http.Requests.Admin.Nodes
@using MoonlightServers.Shared.Http.Responses.Admin.Nodes
@using MoonlightServers.Client.UI.Components.Partials
@inject HttpApiClient HttpApiClient
@attribute [RequirePermission("admin.servers.nodes.get")]
<NavTabs Index="1" TextSize="text-base" Names="@( ["Servers", "Nodes", "Stars", "Manager"])" Links="@( ["/admin/servers", "/admin/servers/nodes", "/admin/servers/stars", "admin/servers/manager"])"/>
<div class="mt-5">
<SmartCrud TItem="DetailNodeResponse"
TCreateForm="CreateNodeRequest"
TUpdateForm="UpdateNodeRequest"
OnConfigure="OnConfigure">
<View Context="ViewContext">
<SmartColumn TItem="DetailNodeResponse" Field="@(x => x.Id)" Title="Id"/>
<SmartColumn TItem="DetailNodeResponse" Field="@(x => x.Name)" Title="Name">
<Template>
<a class="text-blue-500" href="#" @onclick:preventDefault @onclick="() => ViewContext.LaunchDetails(context)">@context.Name</a>
</Template>
</SmartColumn>
<SmartColumn TItem="DetailNodeResponse" Field="@(x => x.Fqdn)" Title="FQDN"/>
<SmartColumn TItem="DetailNodeResponse" Field="@(x => x.Id)">
<Template>
<div class="flex justify-between">
@{
NodeFetchState? fetchState;
lock (StatusCache)
fetchState = StatusCache.ContainsKey(context.Id) ? StatusCache[context.Id] : null;
}
@if (fetchState == null)
{
<div class="text-slate-400">Loading status...</div>
}
else if (fetchState.Response != null)
{
var response = fetchState.Response!;
<div class="text-green-500">
<i class="mr-1 align-middle text-lg bi bi-check-circle-fill text-green-500"></i>
<span>Online</span>
</div>
<div>
<i class="bi bi-cpu text-lg text-slate-400 mr-1"></i>
<span class="text-white">
@(Math.Round(response.CpuUsage.Average(x => x), 2))%
</span>
</div>
<div>
<i class="bi bi-memory text-lg text-slate-400 mr-1"></i>
<span class="text-white">
@Formatter.FormatSize((long)(response.MemoryTotal - response.MemoryAvailable))
<span>/</span>
@Formatter.FormatSize((long)response.MemoryTotal)
</span>
</div>
<div>
<i class="bi bi-hdd text-lg text-slate-400 mr-1"></i>
<span class="text-white">
@Formatter.FormatSize((long)(response.DiskTotal - response.DiskFree))
<span>/</span>
@Formatter.FormatSize((long)response.DiskTotal)
</span>
</div>
}
else if(fetchState.Exception != null)
{
<div class="text-white">
<i class="mr-1 align-middle text-lg bi bi-exclamation-triangle-fill text-red-500"></i>
<span class="mr-1">Offline:</span>
@fetchState.Exception.Message
</div>
}
</div>
</Template>
</SmartColumn>
</View>
<DetailView>
<SmartTabs>
<SmartTab Name="Overview">
<NodeOverview NodeId="@context.Id"/>
</SmartTab>
<SmartTab Name="Allocations">
<AllocationEditor NodeId="@context.Id"/>
</SmartTab>
<SmartTab Name="Logs">
<NodeLogs NodeId="@context.Id"/>
</SmartTab>
</SmartTabs>
</DetailView>
</SmartCrud>
</div>
@code
{
private readonly Dictionary<int, NodeFetchState> StatusCache = new();
private void OnConfigure(CrudOptions<DetailNodeResponse, CreateNodeRequest, UpdateNodeRequest> options)
{
options.Loader = async (page, pageSize) =>
{
var response = await HttpApiClient
.GetJson<PagedResponse<DetailNodeResponse>>($"admin/servers/nodes?page={page}&pageSize={pageSize}");
lock (StatusCache)
StatusCache.Clear();
Task.Run(async () =>
{
foreach (var node in response.Items)
{
try
{
var status = await HttpApiClient.GetJson<StatusNodeResponse>($"admin/servers/nodes/{node.Id}/status");
lock (StatusCache)
{
StatusCache.Add(node.Id, new()
{
Response = status
});
}
}
catch (Exception e)
{
lock (StatusCache)
{
StatusCache.Add(node.Id, new()
{
Exception = e
});
}
}
await InvokeAsync(StateHasChanged);
}
});
return response;
};
options.CreateFunction = async request => await HttpApiClient.Post("admin/servers/nodes", request);
options.UpdateFunction = async (request, item) => await HttpApiClient.Patch($"admin/servers/nodes/{item.Id}", request);
options.DeleteFunction = async item => await HttpApiClient.Delete($"admin/servers/nodes/{item.Id}");
options.ShowCreateAsModal = false;
options.ShowUpdateAsModal = false;
options.ShowDetailsAsModal = false;
options.OnConfigureCreate = option =>
{
option
.DefaultPage
.DefaultSection
.AddProperty(x => x.Name);
option
.DefaultPage
.DefaultSection
.AddProperty(x => x.Fqdn);
option
.DefaultPage
.DefaultSection
.AddProperty(x => x.ApiPort);
option
.DefaultPage
.DefaultSection
.AddProperty(x => x.SslEnabled);
};
options.OnConfigureUpdate = (option, item) =>
{
option
.DefaultPage
.DefaultSection
.AddProperty(x => x.Name);
option
.DefaultPage
.DefaultSection
.AddProperty(x => x.Fqdn);
option
.DefaultPage
.DefaultSection
.AddProperty(x => x.ApiPort);
option
.DefaultPage
.DefaultSection
.AddProperty(x => x.SslEnabled);
};
}
class NodeFetchState
{
public StatusNodeResponse? Response { get; set; }
public Exception? Exception { get; set; }
}
}