Merge branch 'dashboard' into blazor
This commit is contained in:
commit
15f5ba7b47
12 changed files with 446 additions and 13 deletions
19
LANCommander/Extensions/ArrayExtensions.cs
Normal file
19
LANCommander/Extensions/ArrayExtensions.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
namespace LANCommander.Extensions
|
||||
{
|
||||
public static class ArrayExtensions
|
||||
{
|
||||
public static T[] ShiftArrayAndInsert<T>(this T[] array, T input, int max)
|
||||
{
|
||||
if (array == null || array.Length < max)
|
||||
{
|
||||
array = new T[max];
|
||||
}
|
||||
|
||||
Array.Copy(array, 1, array, 0, array.Length - 1);
|
||||
|
||||
array[array.Length - 1] = input;
|
||||
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AntDesign" Version="0.14.3" />
|
||||
<PackageReference Include="AntDesign.Charts" Version="0.3.0" />
|
||||
<PackageReference Include="Blazor-ApexCharts" Version="0.9.18-beta" />
|
||||
<PackageReference Include="BlazorMonaco" Version="3.0.0" />
|
||||
<PackageReference Include="ByteSize" Version="2.1.1" />
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
@using System.Diagnostics;
|
||||
@using LANCommander.Extensions;
|
||||
@using AntDesign.Charts;
|
||||
@using System.Collections.Concurrent;
|
||||
|
||||
<Area @ref="Chart" Config="Config" />
|
||||
|
||||
@code {
|
||||
[Parameter] public int TimerHistory { get; set; }
|
||||
[Parameter] public int TimerInterval { get; set; }
|
||||
|
||||
IChartComponent? Chart;
|
||||
System.Timers.Timer Timer;
|
||||
|
||||
Dictionary<string, double[]> Data = new Dictionary<string, double[]>();
|
||||
|
||||
ConcurrentDictionary<string, PerformanceCounter> PerformanceCounters = new ConcurrentDictionary<string, PerformanceCounter>();
|
||||
|
||||
string JsConfig = @"{
|
||||
meta: {
|
||||
value: {
|
||||
alias: 'Speed',
|
||||
formatter: (v) => humanFileSize(v, true) + '/s'
|
||||
}
|
||||
}
|
||||
}";
|
||||
|
||||
AreaConfig Config = new AreaConfig
|
||||
{
|
||||
Name = "Network Download Rate",
|
||||
Padding = "auto",
|
||||
SeriesField = "series",
|
||||
YField = "value",
|
||||
XField = "index",
|
||||
Animation = false,
|
||||
XAxis = new ValueCatTimeAxis
|
||||
{
|
||||
Visible = false
|
||||
}
|
||||
};
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Timer == null)
|
||||
{
|
||||
Timer = new System.Timers.Timer();
|
||||
|
||||
Timer.Interval = TimerInterval;
|
||||
|
||||
Timer.Elapsed += async (s, e) =>
|
||||
{
|
||||
await RefreshData();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await Chart.UpdateChart(Config, null, null, JsConfig);
|
||||
Timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshData()
|
||||
{
|
||||
var category = new PerformanceCounterCategory("Network Interface");
|
||||
|
||||
foreach (var instance in category.GetInstanceNames())
|
||||
{
|
||||
if (!Data.ContainsKey(instance))
|
||||
Data[instance] = new double[TimerHistory];
|
||||
|
||||
if (!PerformanceCounters.ContainsKey(instance))
|
||||
PerformanceCounters[instance] = new PerformanceCounter("Network Interface", "Bytes Received/sec", instance);
|
||||
|
||||
Data[instance] = Data[instance].ShiftArrayAndInsert((double)PerformanceCounters[instance].NextValue(), TimerHistory);
|
||||
}
|
||||
|
||||
await Chart.ChangeData(Data.SelectMany(x => x.Value.Select((y, i) => new { value = y, index = i, series = x.Key })), true);
|
||||
}
|
||||
}
|
83
LANCommander/Pages/Dashboard/Charts/NetworkUploadRate.razor
Normal file
83
LANCommander/Pages/Dashboard/Charts/NetworkUploadRate.razor
Normal file
|
@ -0,0 +1,83 @@
|
|||
@using System.Diagnostics;
|
||||
@using LANCommander.Extensions;
|
||||
@using AntDesign.Charts;
|
||||
@using System.Collections.Concurrent;
|
||||
|
||||
<Area @ref="Chart" Config="Config" />
|
||||
|
||||
@code {
|
||||
[Parameter] public int TimerHistory { get; set; }
|
||||
[Parameter] public int TimerInterval { get; set; }
|
||||
|
||||
IChartComponent? Chart;
|
||||
System.Timers.Timer Timer;
|
||||
|
||||
Dictionary<string, double[]> Data = new Dictionary<string, double[]>();
|
||||
|
||||
ConcurrentDictionary<string, PerformanceCounter> PerformanceCounters = new ConcurrentDictionary<string, PerformanceCounter>();
|
||||
|
||||
string JsConfig = @"{
|
||||
meta: {
|
||||
value: {
|
||||
alias: 'Speed',
|
||||
formatter: (v) => humanFileSize(v, true) + '/s'
|
||||
}
|
||||
}
|
||||
}";
|
||||
|
||||
AreaConfig Config = new AreaConfig
|
||||
{
|
||||
Name = "Network Upload Rate",
|
||||
Padding = "auto",
|
||||
SeriesField = "series",
|
||||
YField = "value",
|
||||
XField = "index",
|
||||
Animation = false,
|
||||
XAxis = new ValueCatTimeAxis
|
||||
{
|
||||
Visible = false
|
||||
}
|
||||
};
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Timer == null)
|
||||
{
|
||||
Timer = new System.Timers.Timer();
|
||||
|
||||
Timer.Interval = TimerInterval;
|
||||
|
||||
Timer.Elapsed += async (s, e) =>
|
||||
{
|
||||
await RefreshData();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await Chart.UpdateChart(Config, null, null, JsConfig);
|
||||
Timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshData()
|
||||
{
|
||||
var category = new PerformanceCounterCategory("Network Interface");
|
||||
|
||||
foreach (var instance in category.GetInstanceNames())
|
||||
{
|
||||
if (!Data.ContainsKey(instance))
|
||||
Data[instance] = new double[TimerHistory];
|
||||
|
||||
if (!PerformanceCounters.ContainsKey(instance))
|
||||
PerformanceCounters[instance] = new PerformanceCounter("Network Interface", "Bytes Sent/sec", instance);
|
||||
|
||||
Data[instance] = Data[instance].ShiftArrayAndInsert((double)PerformanceCounters[instance].NextValue(), TimerHistory);
|
||||
}
|
||||
|
||||
await Chart.ChangeData(Data.SelectMany(x => x.Value.Select((y, i) => new { value = y, index = i, series = x.Key })), true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
@using System.Diagnostics;
|
||||
@using LANCommander.Extensions;
|
||||
@using AntDesign.Charts;
|
||||
|
||||
<Area @ref="Chart" Config="Config" />
|
||||
|
||||
@code {
|
||||
[Parameter] public int TimerHistory { get; set; }
|
||||
[Parameter] public int TimerInterval { get; set; }
|
||||
IChartComponent? Chart;
|
||||
System.Timers.Timer Timer;
|
||||
|
||||
double[] Data;
|
||||
|
||||
PerformanceCounter PerformanceCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
|
||||
|
||||
string JsConfig = @"{
|
||||
meta: {
|
||||
value: {
|
||||
alias: '% Usage',
|
||||
formatter: (v) => v + '%'
|
||||
}
|
||||
}
|
||||
}";
|
||||
|
||||
AreaConfig Config = new AreaConfig
|
||||
{
|
||||
Name = "Processor Utilization",
|
||||
Padding = "auto",
|
||||
YField = "value",
|
||||
XField = "index",
|
||||
Animation = false,
|
||||
IsPercent = true,
|
||||
YAxis = new ValueAxis
|
||||
{
|
||||
Min = 0,
|
||||
Max = 100
|
||||
},
|
||||
XAxis = new ValueCatTimeAxis
|
||||
{
|
||||
Visible = false
|
||||
}
|
||||
};
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Timer == null)
|
||||
{
|
||||
Timer = new System.Timers.Timer();
|
||||
|
||||
Timer.Interval = TimerInterval;
|
||||
|
||||
Timer.Elapsed += async (s, e) =>
|
||||
{
|
||||
await RefreshData();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await Chart.UpdateChart(Config, null, null, JsConfig);
|
||||
Timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshData()
|
||||
{
|
||||
Data = Data.ShiftArrayAndInsert((double)Math.Ceiling(PerformanceCounter.NextValue()), TimerHistory);
|
||||
|
||||
await Chart.ChangeData(Data.Select((x, i) => new { value = x, index = i }), true);
|
||||
}
|
||||
}
|
63
LANCommander/Pages/Dashboard/Charts/StorageUsage.razor
Normal file
63
LANCommander/Pages/Dashboard/Charts/StorageUsage.razor
Normal file
|
@ -0,0 +1,63 @@
|
|||
@using AntDesign.Charts
|
||||
@using ByteSizeLib
|
||||
|
||||
<Pie Data="Data" Config="Config" JsConfig="@JsConfig" />
|
||||
|
||||
@code {
|
||||
object[] Data;
|
||||
|
||||
string JsConfig = @"{
|
||||
meta: {
|
||||
value: {
|
||||
alias: 'Data Usage',
|
||||
formatter: (v) => humanFileSize(v, true)
|
||||
}
|
||||
},
|
||||
label: {
|
||||
visible: true,
|
||||
type: 'outer-center'
|
||||
}
|
||||
}";
|
||||
|
||||
PieConfig Config = new PieConfig
|
||||
{
|
||||
Radius = 0.8,
|
||||
AngleField = "value",
|
||||
ColorField = "type",
|
||||
};
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var drives = DriveInfo.GetDrives();
|
||||
var root = Path.GetPathRoot(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
var totalStorageSize = drives.Where(d => d.IsReady && d.Name == root).Sum(d => d.TotalSize);
|
||||
var totalAvailableFreeSpace = drives.Where(d => d.IsReady && d.Name == root).Sum(d => d.AvailableFreeSpace);
|
||||
var totalUploadDirectorySize = new DirectoryInfo("Upload").EnumerateFiles().Sum(f => f.Length);
|
||||
var totalSaveDirectorySize = new DirectoryInfo("Save").EnumerateFiles().Sum(f => f.Length);
|
||||
|
||||
Data = new object[]
|
||||
{
|
||||
new {
|
||||
type = "Free",
|
||||
value = totalAvailableFreeSpace
|
||||
},
|
||||
new {
|
||||
type = "Games",
|
||||
value = totalUploadDirectorySize
|
||||
},
|
||||
new
|
||||
{
|
||||
type = "Saves",
|
||||
value = totalSaveDirectorySize
|
||||
},
|
||||
new
|
||||
{
|
||||
type = "Other",
|
||||
value = totalStorageSize - totalAvailableFreeSpace - totalUploadDirectorySize - totalSaveDirectorySize
|
||||
}
|
||||
};
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
39
LANCommander/Pages/Dashboard/Index.razor
Normal file
39
LANCommander/Pages/Dashboard/Index.razor
Normal file
|
@ -0,0 +1,39 @@
|
|||
@page "/"
|
||||
@page "/Dashboard"
|
||||
@using LANCommander.Pages.Dashboard.Charts
|
||||
|
||||
<PageHeader Title="Dashboard" Style="margin-bottom: 24px" />
|
||||
|
||||
<GridRow Gutter="(16, 16)">
|
||||
<GridCol Sm="24" Md="12">
|
||||
<Card Title="Network Upload Rate">
|
||||
<Body>
|
||||
<NetworkDownloadRate TimerHistory="60" TimerInterval="1000" />
|
||||
</Body>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol Sm="24" Md="12">
|
||||
<Card Title="Network Download Rate">
|
||||
<Body>
|
||||
<NetworkUploadRate TimerHistory="60" TimerInterval="1000" />
|
||||
</Body>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol Sm="24" Md="12">
|
||||
<Card Title="CPU Usage (%)">
|
||||
<Body>
|
||||
<ProcessorUtilization TimerHistory="60" TimerInterval="1000" />
|
||||
</Body>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol Sm="24" Md="12">
|
||||
<Card Title="Storage Usage">
|
||||
<Body>
|
||||
<StorageUsage />
|
||||
</Body>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</GridRow>
|
|
@ -22,10 +22,13 @@
|
|||
</div>
|
||||
|
||||
<script src="~/_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="~/lib/antv/g2plot/dist/g2plot.js"></script>
|
||||
<script src="~/_content/AntDesign/js/ant-design-blazor.js"></script>
|
||||
<script src="~/_content/AntDesign.Charts/ant-design-charts-blazor.js"></script>
|
||||
<script src="~/_framework/blazor.server.js"></script>
|
||||
<script src="~/_content/BlazorMonaco/jsInterop.js"></script>
|
||||
<script src="~/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
|
||||
<script src="~/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
|
||||
<script src="~/js/site.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -37,19 +37,27 @@
|
|||
"min/vs/loader.js",
|
||||
"min/vs/base/browser/ui/codicons/codicon/codicon.ttf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider": "cdnjs",
|
||||
"library": "tabler-icons@1.35.0",
|
||||
"destination": "wwwroot/lib/tabler-icons/",
|
||||
"files": [
|
||||
"iconfont/tabler-icons.min.css",
|
||||
"iconfont/fonts/tabler-icons.eot",
|
||||
"iconfont/fonts/tabler-icons.ttf",
|
||||
"iconfont/fonts/tabler-icons.woff",
|
||||
"iconfont/fonts/tabler-icons.woff2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider": "unpkg",
|
||||
"library": "@antv/g2plot@1.1.28",
|
||||
"destination": "wwwroot/lib/antv/g2plot/",
|
||||
"files": [
|
||||
"dist/g2plot.js",
|
||||
"dist/g2plot.js.map"
|
||||
]
|
||||
}
|
||||
,
|
||||
{
|
||||
"provider": "cdnjs",
|
||||
"library": "tabler-icons@1.35.0",
|
||||
"destination": "wwwroot/lib/tabler-icons/",
|
||||
"files": [
|
||||
"iconfont/tabler-icons.min.css",
|
||||
"iconfont/fonts/tabler-icons.eot",
|
||||
"iconfont/fonts/tabler-icons.ttf",
|
||||
"iconfont/fonts/tabler-icons.woff",
|
||||
"iconfont/fonts/tabler-icons.woff2"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -2,3 +2,24 @@
|
|||
// for details on configuring this project to bundle and minify static web assets.
|
||||
|
||||
// Write your JavaScript code.
|
||||
function humanFileSize(bytes, si = false, dp = 1) {
|
||||
const thresh = si ? 1000 : 1024;
|
||||
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
return bytes + ' B';
|
||||
}
|
||||
|
||||
const units = si
|
||||
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||
let u = -1;
|
||||
const r = 10 ** dp;
|
||||
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
||||
|
||||
|
||||
return bytes.toFixed(dp) + ' ' + units[u];
|
||||
}
|
37
LANCommander/wwwroot/lib/antv/g2plot/dist/g2plot.js
vendored
Normal file
37
LANCommander/wwwroot/lib/antv/g2plot/dist/g2plot.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
LANCommander/wwwroot/lib/antv/g2plot/dist/g2plot.js.map
vendored
Normal file
1
LANCommander/wwwroot/lib/antv/g2plot/dist/g2plot.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue