Merge pull request #297 from Moonlight-Panel/AddLiveStatistics
Add live statistics
This commit is contained in:
commit
71a8839f5f
6 changed files with 268 additions and 4 deletions
|
@ -156,6 +156,32 @@ public static class Formatter
|
|||
return (i / (1024D * 1024D)).Round(2) + " GB";
|
||||
}
|
||||
}
|
||||
|
||||
public static double CalculateAverage(List<double> values)
|
||||
{
|
||||
if (values == null || values.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("The list cannot be null or empty.");
|
||||
}
|
||||
|
||||
double sum = 0;
|
||||
foreach (double value in values)
|
||||
{
|
||||
sum += value;
|
||||
}
|
||||
|
||||
return sum / values.Count;
|
||||
}
|
||||
|
||||
public static double CalculatePercentage(double part, double total)
|
||||
{
|
||||
if (total == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (part / total) * 100;
|
||||
}
|
||||
|
||||
public static RenderFragment FormatLineBreaks(string content)
|
||||
{
|
||||
|
|
|
@ -410,10 +410,17 @@ public static class Permissions
|
|||
|
||||
public static Permission AdminChangelog = new()
|
||||
{
|
||||
Index = 59,
|
||||
Index = 60,
|
||||
Name = "Admin changelog",
|
||||
Description = "View the changelog"
|
||||
};
|
||||
|
||||
public static Permission AdminStatisticsLive = new()
|
||||
{
|
||||
Index = 61,
|
||||
Name = "Admin statistics live",
|
||||
Description = "View the live statistics"
|
||||
};
|
||||
|
||||
public static Permission? FromString(string name)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Moonlight.App.Extensions
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Services
|
||||
|
@ -48,7 +48,6 @@
|
|||
<link rel="stylesheet" type="text/css" href="/_content/XtermBlazor/XtermBlazor.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/_content/Blazor.ContextMenu/blazorContextMenu.min.css"/>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/toastr.css"/>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
|
@ -98,6 +97,9 @@
|
|||
<script src="/assets/js/popper.min.js"></script>
|
||||
<script src="/assets/js/bootstrap.min.js"></script>
|
||||
|
||||
<script src="/_content/Blazor-ApexCharts/js/apex-charts.min.js"></script>
|
||||
<script src="/_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>
|
||||
|
||||
<script src="/_content/XtermBlazor/XtermBlazor.min.js"></script>
|
||||
<script src="/_content/BlazorTable/BlazorTable.min.js"></script>
|
||||
<script src="/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<div class="card mb-5 mb-xl-10">
|
||||
<div class="card-body pt-0 pb-0">
|
||||
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/statistics">
|
||||
<TL>Overview</TL>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/statistics/live">
|
||||
<TL>Live data</TL>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public int Index { get; set; } = 0;
|
||||
}
|
|
@ -6,13 +6,16 @@
|
|||
@using ApexCharts
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.Shared.Components.Navigations
|
||||
|
||||
@inject StatisticsViewService StatisticsViewService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminStatistics))]
|
||||
|
||||
<div class="row mt-4 mb-2">
|
||||
<AdminStatisticsNavigation />
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-12 col-lg-6 col-xl">
|
||||
<div class="card card-body">
|
||||
<select class="form-select" @bind="TimeSpanBind">
|
204
Moonlight/Shared/Views/Admin/Statistics/Live.razor
Normal file
204
Moonlight/Shared/Views/Admin/Statistics/Live.razor
Normal file
|
@ -0,0 +1,204 @@
|
|||
@page "/admin/statistics/live"
|
||||
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Services.Sessions
|
||||
@using Moonlight.Shared.Components.Navigations
|
||||
|
||||
@inject NodeService NodeService
|
||||
@inject Repository<Node> NodeRepository
|
||||
@inject IServiceScopeFactory ServiceScopeFactory
|
||||
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminStatisticsLive))]
|
||||
|
||||
<AdminStatisticsNavigation />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-3 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-header pt-5">
|
||||
<div class="card-title d-flex flex-column">
|
||||
<span class="fs-2hx fw-bold text-white me-2 lh-1 ls-n2">@(Math.Round(TotalCpuUsed, 2))% / 100%</span>
|
||||
<span class="text-white opacity-75 pt-1 fw-semibold fs-6">
|
||||
<TL>Total cpu load</TL>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-end pt-0">
|
||||
<div class="d-flex align-items-center flex-column mt-3 w-100">
|
||||
@{
|
||||
var cpuPercent = Math.Round(Formatter.CalculatePercentage(TotalCpuUsed, 100));
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-end fw-bold fs-6 text-white opacity-75 w-100 mt-auto mb-2">
|
||||
<span>@(cpuPercent)%</span>
|
||||
</div>
|
||||
|
||||
<div class="h-8px mx-3 w-100 bg-white bg-opacity-50 rounded">
|
||||
<div class="bg-@(GetStateColor(cpuPercent)) rounded h-8px" role="progressbar" style="width: @(cpuPercent)%;" aria-valuenow="@(cpuPercent)" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-3 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-header pt-5">
|
||||
<div class="card-title d-flex flex-column">
|
||||
<span class="fs-2hx fw-bold text-white me-2 lh-1 ls-n2">@(ByteSizeValue.FromKiloBytes(TotalMemoryUsed).GigaBytes)GB / @(ByteSizeValue.FromKiloBytes(TotalMemory).GigaBytes)GB</span>
|
||||
<span class="text-white opacity-75 pt-1 fw-semibold fs-6">
|
||||
<TL>Total memory load</TL>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-end pt-0">
|
||||
<div class="d-flex align-items-center flex-column mt-3 w-100">
|
||||
@{
|
||||
var memoryPercent = Math.Round(Formatter.CalculatePercentage(TotalMemoryUsed, TotalMemory));
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-end fw-bold fs-6 text-white opacity-75 w-100 mt-auto mb-2">
|
||||
<span>@(memoryPercent)%</span>
|
||||
</div>
|
||||
|
||||
<div class="h-8px mx-3 w-100 bg-white bg-opacity-50 rounded">
|
||||
<div class="bg-@(GetStateColor(memoryPercent)) rounded h-8px" role="progressbar" style="width: @(memoryPercent)%;" aria-valuenow="@(memoryPercent)" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-3 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body pt-5">
|
||||
<div class="card-title d-flex flex-column">
|
||||
<span class="fs-2hx fw-bold text-white me-2 lh-1 ls-n2">@(Users)</span>
|
||||
<span class="text-white opacity-75 pt-1 fw-semibold fs-6">
|
||||
<TL>Total user count</TL>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-3 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body pt-5">
|
||||
<div class="card-title d-flex flex-column">
|
||||
<span class="fs-2hx fw-bold text-white me-2 lh-1 ls-n2">@(Sessions)</span>
|
||||
<span class="text-white opacity-75 pt-1 fw-semibold fs-6">
|
||||
<TL>Total session count</TL>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-3 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body pt-5">
|
||||
<div class="card-title d-flex flex-column">
|
||||
<span class="fs-2hx fw-bold text-white me-2 lh-1 ls-n2">@(ActiveUsers)</span>
|
||||
<span class="text-white opacity-75 pt-1 fw-semibold fs-6">
|
||||
<TL>Total active user count</TL>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
private long TotalMemoryUsed;
|
||||
private long TotalMemory;
|
||||
|
||||
private double TotalCpuUsed;
|
||||
|
||||
private int Users;
|
||||
private int ActiveUsers;
|
||||
private int Sessions;
|
||||
|
||||
protected override Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await Monitor();
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Monitor()
|
||||
{
|
||||
async Task Nodes()
|
||||
{
|
||||
TotalMemory = 0;
|
||||
TotalMemoryUsed = 0;
|
||||
|
||||
var cpuValues = new List<double>();
|
||||
|
||||
foreach (var node in NodeRepository.Get().ToArray())
|
||||
{
|
||||
try
|
||||
{
|
||||
var metrics = await NodeService.GetMemoryMetrics(node);
|
||||
|
||||
TotalMemory += metrics.Total;
|
||||
TotalMemoryUsed += metrics.Used;
|
||||
|
||||
var cpuMetrics = await NodeService.GetCpuMetrics(node);
|
||||
cpuValues.Add(cpuMetrics.CpuUsage);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
TotalCpuUsed = Formatter.CalculateAverage(cpuValues);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
async Task UsersAndSessions()
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
|
||||
var userRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
|
||||
var sessionService = scope.ServiceProvider.GetRequiredService<SessionServerService>();
|
||||
|
||||
Users = userRepo.Get().Count();
|
||||
Sessions = (await sessionService.GetSessions()).Length;
|
||||
ActiveUsers = userRepo
|
||||
.Get()
|
||||
.Count(x => x.LastVisitedAt > DateTime.UtcNow.AddDays(-1));
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
await Nodes();
|
||||
await UsersAndSessions();
|
||||
}
|
||||
|
||||
private string GetStateColor(double percent)
|
||||
{
|
||||
if (percent < 60)
|
||||
return "success";
|
||||
else if (percent >= 60 && percent < 80)
|
||||
return "warning";
|
||||
else
|
||||
return "danger";
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue