Refactored ui. Improved console experience. Added command endpoint
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
@using System.Text.Json.Serialization
|
||||
@using Microsoft.Extensions.Logging
|
||||
@using XtermBlazor
|
||||
@inherits MoonCore.Blazor.FlyonUi.Modals.Components.BaseModal
|
||||
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject ILogger<FullScreenModal> Logger
|
||||
|
||||
@implements IAsyncDisposable
|
||||
|
||||
<div class="bg-black p-2 relative w-full h-[90vh] rounded-lg">
|
||||
@if (IsInitialized)
|
||||
{
|
||||
<Xterm @ref="Terminal"
|
||||
Addons="Parent.Addons"
|
||||
Options="Parent.Options"
|
||||
Class="h-full w-full"
|
||||
OnFirstRender="HandleFirstRender"/>
|
||||
}
|
||||
|
||||
<div class="absolute top-4 right-4">
|
||||
<button @onclick="Hide" class="btn btn-error btn-square">
|
||||
<i class="icon-x text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public XtermConsole Parent { get; set; }
|
||||
|
||||
private bool IsInitialized = false;
|
||||
private bool IsReadyToWrite = false;
|
||||
private Xterm Terminal;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!firstRender)
|
||||
return;
|
||||
|
||||
// Initialize addons
|
||||
|
||||
try
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlightServers.loadAddons");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError("An error occured while initializing addons: {e}", e);
|
||||
}
|
||||
|
||||
// Subscribe to parent events
|
||||
Parent.OnWrite += HandleWrite;
|
||||
|
||||
IsInitialized = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task HandleFirstRender()
|
||||
{
|
||||
IsReadyToWrite = true;
|
||||
|
||||
try
|
||||
{
|
||||
await Terminal.Addon("addon-fit").InvokeVoidAsync("fit");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError("An error occured while calling addons: {e}", e);
|
||||
}
|
||||
|
||||
var outputToWrite = string.Concat(Parent.OutputCache.ToArray());
|
||||
await Terminal.Write(outputToWrite);
|
||||
}
|
||||
|
||||
private async Task HandleWrite(string content)
|
||||
{
|
||||
if (!IsReadyToWrite)
|
||||
return;
|
||||
|
||||
await Terminal.Write(content);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
Parent.OnWrite -= HandleWrite;
|
||||
await Terminal.DisposeAsync();
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="col-span-1">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Actions
|
||||
<span class="card-title">Actions</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="flex flex-col gap-y-3">
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
@inject NodeService NodeService
|
||||
@inject ToastService ToastService
|
||||
@inject ILogger<OverviewNodeUpdate> Logger
|
||||
@inject ILogger<Overview> Logger
|
||||
|
||||
@implements IDisposable
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</p>
|
||||
<i class="icon-cpu text-4xl text-primary"></i>
|
||||
</div>
|
||||
<div class="text-base text-slate-300">
|
||||
<div class="text-base text-base-content/90">
|
||||
<span class="truncate">
|
||||
CPU: @Statistics.Cpu.Model
|
||||
</span>
|
||||
@@ -42,7 +42,7 @@
|
||||
</p>
|
||||
<i class="icon-memory-stick text-4xl text-primary"></i>
|
||||
</div>
|
||||
<div class="text-base text-slate-300">
|
||||
<div class="text-base text-base-content/90">
|
||||
Memory
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,7 +56,7 @@
|
||||
</div>
|
||||
<i class="icon-shapes text-4xl text-primary"></i>
|
||||
</div>
|
||||
<div class="text-base text-slate-300">
|
||||
<div class="text-base text-base-content/90">
|
||||
Swap
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,7 +76,7 @@
|
||||
var percentRounded = Math.Round(usage, 2);
|
||||
|
||||
<div class="flex flex-row items-center col-span-1">
|
||||
<div class="text-sm text-slate-300 me-1.5 grow-0 flex flex-col">
|
||||
<div class="text-sm text-base-content/90 me-1.5 grow-0 flex flex-col">
|
||||
<span>#@(i)</span>
|
||||
</div>
|
||||
<div class="grow">
|
||||
@@ -108,7 +108,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-slate-300 mt-2.5 flex flex-col">
|
||||
<div class="text-sm text-base-content/90 mt-2.5 flex flex-col">
|
||||
<div>
|
||||
Device: <span class="font-semibold">@disk.Device</span> - Mounted at: <span class="font-semibold truncate">@disk.MountPath</span>
|
||||
</div>
|
||||
@@ -136,7 +136,7 @@
|
||||
</p>
|
||||
<i class="icon-gallery-horizontal-end text-4xl text-primary"></i>
|
||||
</div>
|
||||
<div class="text-base text-slate-300">
|
||||
<div class="text-base text-base-content/90">
|
||||
Images
|
||||
</div>
|
||||
</div>
|
||||
@@ -148,7 +148,7 @@
|
||||
</p>
|
||||
<i class="icon-container text-4xl text-primary"></i>
|
||||
</div>
|
||||
<div class="text-base text-slate-300">
|
||||
<div class="text-base text-base-content/90">
|
||||
Containers
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,7 +160,7 @@
|
||||
</p>
|
||||
<i class="icon-hard-hat text-4xl text-primary"></i>
|
||||
</div>
|
||||
<div class="text-base text-slate-300">
|
||||
<div class="text-base text-base-content/90">
|
||||
Build Cache
|
||||
</div>
|
||||
</div>
|
||||
@@ -8,8 +8,8 @@
|
||||
@inject ILogger<ServerCard> Logger
|
||||
|
||||
@{
|
||||
var gradient = "from-base-content/20";
|
||||
var border = "border-base-content";
|
||||
var gradient = "from-base-100/20";
|
||||
var border = "border-base-content/80";
|
||||
|
||||
if (IsLoaded && !IsFailed)
|
||||
{
|
||||
@@ -20,7 +20,7 @@
|
||||
ServerState.Starting => "from-warning/20",
|
||||
ServerState.Stopping => "from-warning/20",
|
||||
ServerState.Online => "from-success/20",
|
||||
_ => "from-base-content/20"
|
||||
_ => "from-base-100"
|
||||
};
|
||||
|
||||
border = Status.State switch
|
||||
@@ -30,13 +30,13 @@
|
||||
ServerState.Starting => "border-warning",
|
||||
ServerState.Stopping => "border-warning",
|
||||
ServerState.Online => "border-success",
|
||||
_ => "border-base-content"
|
||||
_ => "border-base-content/80"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
<a href="/servers/@Server.Id"
|
||||
class="w-full bg-gradient-to-r @gradient to-base-content/75 to-25% px-5 py-3.5 rounded-xl border-l-8 @border">
|
||||
class="w-full bg-gradient-to-r @gradient to-base-100/75 to-25% px-5 py-3.5 rounded-xl border-l-8 @border">
|
||||
<div class="grid grid-cols-6">
|
||||
<div class="flex items-center col-span-6 sm:col-span-2 2xl:col-span-1">
|
||||
<div class="bg-base-content/10 bg-opacity-45 py-1 px-2 rounded-lg flex items-center">
|
||||
@@ -54,7 +54,7 @@
|
||||
Status.State is ServerState.Starting or ServerState.Stopping or ServerState.Online
|
||||
)
|
||||
{
|
||||
<div class="bg-base-content/35 py-1 px-2 rounded-lg flex flex-row">
|
||||
<div class="bg-base-200/75 py-1 px-2 rounded-lg flex flex-row">
|
||||
<div>
|
||||
<i class="icon-cpu"></i>
|
||||
</div>
|
||||
@@ -62,7 +62,7 @@
|
||||
<div class="ms-3">@(Stats.CpuUsage)%</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-base-content/35 py-1 px-2 rounded-lg flex flex-row">
|
||||
<div class="bg-base-200/75 py-1 px-2 rounded-lg flex flex-row">
|
||||
<div>
|
||||
<i class="icon-memory-stick"></i>
|
||||
</div>
|
||||
@@ -70,7 +70,7 @@
|
||||
<div class="ms-3">@(Formatter.FormatSize(Stats.MemoryUsage)) / @(Formatter.FormatSize(ByteConverter.FromMegaBytes(Server.Memory).Bytes))</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-base-content/35 py-1 px-2 rounded-lg flex flex-row">
|
||||
<div class="bg-base-200/75 py-1 px-2 rounded-lg flex flex-row">
|
||||
<div>
|
||||
<i class="icon-hard-drive"></i>
|
||||
</div>
|
||||
@@ -82,7 +82,7 @@
|
||||
{
|
||||
if (!IsLoaded)
|
||||
{
|
||||
<div class="bg-base-content/35 py-1 px-2 rounded-lg flex flex-row text-gray-700">
|
||||
<div class="bg-base-200/75 py-1 px-2 rounded-lg flex flex-row text-gray-700">
|
||||
<div>
|
||||
<i class="icon-loader"></i>
|
||||
</div>
|
||||
@@ -92,7 +92,7 @@
|
||||
}
|
||||
else if (IsFailed)
|
||||
{
|
||||
<div class="bg-base-content/35 py-1 px-2 rounded-lg flex flex-row text-error">
|
||||
<div class="bg-base-200/75 py-1 px-2 rounded-lg flex flex-row text-error">
|
||||
<div>
|
||||
<i class="icon-cable"></i>
|
||||
</div>
|
||||
@@ -102,7 +102,7 @@
|
||||
}
|
||||
else if (IsLoaded && !IsFailed && Status.State is ServerState.Offline)
|
||||
{
|
||||
<div class="bg-base-content/35 py-1 px-2 rounded-lg flex flex-row text-error">
|
||||
<div class="bg-base-200/75 py-1 px-2 rounded-lg flex flex-row text-error">
|
||||
<div>
|
||||
<i class="icon-power-off"></i>
|
||||
</div>
|
||||
@@ -112,7 +112,7 @@
|
||||
}
|
||||
else if (IsLoaded && !IsFailed && Status.State is ServerState.Installing)
|
||||
{
|
||||
<div class="bg-base-content/35 py-1 px-2 rounded-lg flex flex-row text-primary">
|
||||
<div class="bg-base-200/75 py-1 px-2 rounded-lg flex flex-row text-primary">
|
||||
<div>
|
||||
<i class="icon-hammer"></i>
|
||||
</div>
|
||||
@@ -127,7 +127,7 @@
|
||||
|
||||
@if (Server.Share != null)
|
||||
{
|
||||
<div class="bg-base-content/35 py-1 px-2 rounded-lg flex flex-row col-span-2">
|
||||
<div class="bg-base-200/75 py-1 px-2 rounded-lg flex flex-row col-span-2">
|
||||
<div>
|
||||
<i class="icon-share-2"></i>
|
||||
</div>
|
||||
@@ -137,7 +137,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="bg-base-content/35 py-1 px-2 rounded-lg flex flex-row">
|
||||
<div class="bg-base-200/75 py-1 px-2 rounded-lg flex flex-row">
|
||||
<div>
|
||||
<i class="icon-sparkles"></i>
|
||||
</div>
|
||||
@@ -145,7 +145,7 @@
|
||||
<div class="ms-3">@Server.StarName</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-base-content/35 py-1 px-2 rounded-lg flex flex-row">
|
||||
<div class="bg-base-200/75 py-1 px-2 rounded-lg flex flex-row">
|
||||
<div>
|
||||
<i class="icon-database"></i>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
@using Microsoft.AspNetCore.SignalR.Client
|
||||
@using MoonlightServers.Frontend.Services
|
||||
|
||||
@inherits BaseServerTab
|
||||
|
||||
@inject ServerService ServerService
|
||||
|
||||
<div class="h-44">
|
||||
<XtermConsole @ref="XtermConsole" OnAfterInitialized="OnAfterConsoleInitialized"/>
|
||||
<XtermConsole @ref="XtermConsole"
|
||||
OnAfterInitialized="OnAfterConsoleInitialized"
|
||||
CommandHistory="Parent.CommandHistory"
|
||||
OnCommand="OnCommand"/>
|
||||
</div>
|
||||
|
||||
@code
|
||||
@@ -16,10 +22,10 @@
|
||||
|
||||
HubConnection.On<string>("ConsoleOutput", async content =>
|
||||
{
|
||||
if(XtermConsole != null)
|
||||
if (XtermConsole != null)
|
||||
await XtermConsole.Write(content);
|
||||
});
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -27,4 +33,7 @@
|
||||
{
|
||||
await XtermConsole!.Write(InitialConsoleMessage);
|
||||
}
|
||||
|
||||
private async Task OnCommand(string command)
|
||||
=> await ServerService.RunCommand(Server.Id, command + "\n");
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
else
|
||||
{
|
||||
<WButton CssClasses="btn btn-primary" OnClick="Reinstall">
|
||||
<i class="align-middle icon-hammer me-1"></i>
|
||||
<i class="align-middle icon-hammer"></i>
|
||||
<span class="align-middle">Reinstall</span>
|
||||
</WButton>
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="grid grid-col-1 gap-y-3">
|
||||
@foreach (var share in Shares)
|
||||
{
|
||||
<div class="col-span-1 card card-body px-5 py-3 flex flex-row items-center justify-between">
|
||||
<div class="col-span-1 card card-body py-3 flex flex-row items-center justify-between">
|
||||
<div class="flex justify-start font-semibold">
|
||||
<i class="icon-user-round me-2"></i>
|
||||
<span>@share.Username</span>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
||||
@foreach (var variable in Variables)
|
||||
{
|
||||
<div class="sm:col-span-2 card card-body p-5">
|
||||
<div class="sm:col-span-2 card card-body">
|
||||
<label class="block text-sm font-medium leading-6 text-base-content">
|
||||
@variable.Name
|
||||
</label>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
@using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations
|
||||
@using MoonlightServers.Shared.Http.Responses.Admin.Servers
|
||||
@using MoonCore.Blazor.FlyonUi.Forms
|
||||
@using MoonlightServers.Frontend.UI.Views.Admin.All
|
||||
|
||||
@inject HttpApiClient ApiClient
|
||||
|
||||
@@ -12,7 +13,7 @@
|
||||
<label class="block text-sm font-medium leading-6 text-base-content">Allocations</label>
|
||||
<div class="mt-2">
|
||||
<InputMultipleItem TItem="NodeAllocationResponse"
|
||||
Value="Allocations"
|
||||
Value="Parent.Allocations"
|
||||
DisplayField="@(x => $"{x.IpAddress}:{x.Port}")"
|
||||
SearchField="@(x => $"{x.IpAddress}:{x.Port}")"
|
||||
ItemSource="Loader">
|
||||
@@ -25,7 +26,7 @@
|
||||
{
|
||||
[Parameter] public UpdateServerRequest Request { get; set; }
|
||||
[Parameter] public ServerResponse Server { get; set; }
|
||||
[Parameter] public List<NodeAllocationResponse> Allocations { get; set; }
|
||||
[Parameter] public Update Parent { get; set; }
|
||||
|
||||
private async Task<NodeAllocationResponse[]> Loader()
|
||||
{
|
||||
@@ -3,6 +3,7 @@
|
||||
@using MoonCore.Models
|
||||
@using Moonlight.Shared.Http.Responses.Admin.Users
|
||||
@using MoonCore.Blazor.FlyonUi.Forms
|
||||
@using MoonlightServers.Frontend.UI.Views.Admin.All
|
||||
|
||||
@inject HttpApiClient ApiClient
|
||||
|
||||
@@ -20,7 +21,7 @@
|
||||
<InputItem TItem="UserResponse"
|
||||
DisplayField="@(x => x.Username)"
|
||||
SearchField="@(x => x.Username)"
|
||||
@bind-Value="Owner"
|
||||
@bind-Value="Parent.Owner"
|
||||
ItemSource="Loader">
|
||||
</InputItem>
|
||||
|
||||
@@ -61,7 +62,7 @@
|
||||
@code
|
||||
{
|
||||
[Parameter] public UpdateServerRequest Request { get; set; }
|
||||
[Parameter] public UserResponse Owner { get; set; }
|
||||
[Parameter] public Update Parent { get; set; }
|
||||
|
||||
private async Task<UserResponse[]> Loader()
|
||||
{
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<LazyLoader Load="Load">
|
||||
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
|
||||
@foreach (var variable in Variables)
|
||||
@foreach (var variable in ServerVariables)
|
||||
{
|
||||
var reqVariable = Request.Variables.FirstOrDefault(x => x.Key == variable.Key);
|
||||
var starVariable = StarVariables.FirstOrDefault(x => x.Key == variable.Key);
|
||||
@@ -46,7 +46,7 @@
|
||||
[Parameter] public ServerResponse Server { get; set; }
|
||||
|
||||
private StarVariableDetailResponse[] StarVariables;
|
||||
private ServerVariableResponse[] Variables;
|
||||
private ServerVariableResponse[] ServerVariables;
|
||||
|
||||
private async Task Load(LazyLoader _)
|
||||
{
|
||||
@@ -56,7 +56,7 @@
|
||||
)
|
||||
);
|
||||
|
||||
Variables = await PagedData<ServerVariableResponse>.All(async (page, pageSize) =>
|
||||
ServerVariables = await PagedData<ServerVariableResponse>.All(async (page, pageSize) =>
|
||||
await ApiClient.GetJson<PagedData<ServerVariableResponse>>(
|
||||
$"api/admin/servers/{Server.Id}/variables?page={page}&pageSize={pageSize}"
|
||||
)
|
||||
@@ -7,7 +7,7 @@
|
||||
@using MoonlightServers.Shared.Http.Requests.Admin.Stars
|
||||
@using MoonlightServers.Shared.Models
|
||||
|
||||
@inject ILogger<ParseConfigStarUpdate> Logger
|
||||
@inject ILogger<ParseConfig> Logger
|
||||
@inject ModalService ModalService
|
||||
@inject AlertService AlertService
|
||||
@inject ToastService ToastService
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<div class="grid sm:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||
@foreach (var variable in Variables)
|
||||
@foreach (var variable in CurrentVariables)
|
||||
{
|
||||
<div class="col-span-1 card card-body p-2.5">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -48,7 +48,7 @@
|
||||
{
|
||||
[Parameter] public StarDetailResponse Star { get; set; }
|
||||
|
||||
private StarVariableDetailResponse[] Variables;
|
||||
private StarVariableDetailResponse[] CurrentVariables;
|
||||
private LazyLoader LazyLoader;
|
||||
|
||||
private async Task Load(LazyLoader arg)
|
||||
@@ -57,7 +57,7 @@
|
||||
$"api/admin/servers/stars/{Star.Id}/variables?page=0&pageSize=50"
|
||||
);
|
||||
|
||||
Variables = pagedVariables.Items;
|
||||
CurrentVariables = pagedVariables.Items;
|
||||
}
|
||||
|
||||
private async Task AddVariable()
|
||||
@@ -1,31 +1,72 @@
|
||||
@using System.Collections.Concurrent
|
||||
@using Microsoft.Extensions.Logging
|
||||
@using MoonCore.Blazor.FlyonUi.Modals
|
||||
@using MoonCore.Helpers
|
||||
@using XtermBlazor
|
||||
@using MoonCore.Blazor.FlyonUi.Components
|
||||
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject ModalService ModalService
|
||||
@inject ILogger<XtermConsole> Logger
|
||||
|
||||
<div class="bg-background rounded-lg p-2">
|
||||
@implements IAsyncDisposable
|
||||
|
||||
<div class="bg-black rounded-lg p-2 relative">
|
||||
@if (IsInitialized)
|
||||
{
|
||||
<Xterm @ref="Terminal"
|
||||
Addons="Addons"
|
||||
Options="Options"
|
||||
Class="h-full w-full"
|
||||
OnFirstRender="HandleFirstRender"/>
|
||||
}
|
||||
|
||||
<div class="flex flex-row w-full mt-1.5">
|
||||
<input @bind="CommandInput" @onkeyup="HandleKey" type="text" placeholder="Type here..." class="input grow"/>
|
||||
<WButton OnClick="_ => SubmitCommand()" CssClasses="btn btn-square btn-primary grow-0 ms-1.5">
|
||||
<i class="icon-send-horizontal text-lg"></i>
|
||||
</WButton>
|
||||
</div>
|
||||
|
||||
<div class="absolute top-4 right-4">
|
||||
<div class="flex flex-col gap-y-1.5">
|
||||
@if (IsPaused)
|
||||
{
|
||||
<button @onclick="TogglePause" class="btn btn-primary btn-square">
|
||||
<i class="icon-play text-lg"></i>
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button @onclick="TogglePause" class="btn btn-secondary btn-square">
|
||||
<i class="icon-pause text-lg"></i>
|
||||
</button>
|
||||
}
|
||||
|
||||
<button @onclick="OpenFullscreen" class="btn btn-secondary btn-square">
|
||||
<i class="icon-maximize text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public Func<Task>? OnAfterInitialized { get; set; }
|
||||
[Parameter] public Func<Task>? OnFirstRender { get; set; }
|
||||
|
||||
private Xterm Terminal;
|
||||
private bool IsInitialized = false;
|
||||
private bool HadFirstRender = false;
|
||||
[Parameter] public bool ShowActions { get; set; } = true;
|
||||
[Parameter] public bool ShowInput { get; set; } = false;
|
||||
[Parameter] public int MaxOutputCacheSize { get; set; } = 250;
|
||||
[Parameter] public Func<string, Task>? OnCommand { get; set; }
|
||||
|
||||
private readonly Queue<string> MessageCache = new();
|
||||
private readonly HashSet<string> Addons = ["addon-fit"];
|
||||
private readonly TerminalOptions Options = new()
|
||||
[Parameter] public IList<string> CommandHistory { get; set; } = new ConcurrentList<string>();
|
||||
|
||||
public event Func<string, Task>? OnWrite;
|
||||
|
||||
private Xterm Terminal;
|
||||
public HashSet<string> Addons { get; } = ["addon-fit"];
|
||||
|
||||
public TerminalOptions Options { get; } = new()
|
||||
{
|
||||
CursorBlink = false,
|
||||
CursorStyle = CursorStyle.Bar,
|
||||
@@ -36,17 +77,23 @@
|
||||
Theme =
|
||||
{
|
||||
Background = "#000000"
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
public ConcurrentList<string> OutputCache { get; private set; } = new();
|
||||
public ConcurrentList<string> WriteQueue { get; private set; } = new();
|
||||
private int CommandIndex = -1;
|
||||
private bool IsReadyToWrite = false;
|
||||
private bool IsPaused = false;
|
||||
private bool IsInitialized = false;
|
||||
|
||||
private string CommandInput = "";
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if(!firstRender)
|
||||
if (!firstRender)
|
||||
return;
|
||||
|
||||
// Initialize addons
|
||||
|
||||
try
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlightServers.loadAddons");
|
||||
@@ -59,7 +106,7 @@
|
||||
IsInitialized = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
if(OnAfterInitialized != null)
|
||||
if (OnAfterInitialized != null)
|
||||
await OnAfterInitialized.Invoke();
|
||||
}
|
||||
|
||||
@@ -74,13 +121,13 @@
|
||||
Logger.LogError("An error occured while calling addons: {e}", e);
|
||||
}
|
||||
|
||||
HadFirstRender = true;
|
||||
IsReadyToWrite = true;
|
||||
|
||||
// Write out cache
|
||||
while (MessageCache.Count > 0)
|
||||
{
|
||||
await Terminal.Write(MessageCache.Dequeue());
|
||||
}
|
||||
// Write queued content since initialisation started
|
||||
var queueContent = string.Concat(WriteQueue);
|
||||
WriteQueue.Clear();
|
||||
|
||||
await Terminal.Write(queueContent);
|
||||
|
||||
if (OnFirstRender != null)
|
||||
await OnFirstRender.Invoke();
|
||||
@@ -89,10 +136,103 @@
|
||||
public async Task Write(string content)
|
||||
{
|
||||
// We cache messages here as there is the chance that the console isn't ready for input while receiving write tasks
|
||||
|
||||
if (HadFirstRender)
|
||||
await Terminal.Write(content);
|
||||
|
||||
if (IsReadyToWrite && !IsPaused)
|
||||
await HandleWrite(content);
|
||||
else
|
||||
MessageCache.Enqueue(content);
|
||||
WriteQueue.Add(content);
|
||||
}
|
||||
|
||||
private async Task HandleWrite(string content)
|
||||
{
|
||||
// Update output cache and prune it if required
|
||||
if (OutputCache.Count > MaxOutputCacheSize)
|
||||
{
|
||||
for (var i = 0; i < 50; i++)
|
||||
OutputCache.RemoveAt(i);
|
||||
}
|
||||
|
||||
OutputCache.Add(content);
|
||||
|
||||
// Trigger events
|
||||
if (OnWrite != null)
|
||||
await OnWrite.Invoke(content);
|
||||
|
||||
// Write in own terminal
|
||||
await Terminal.Write(content);
|
||||
}
|
||||
|
||||
private async Task OpenFullscreen()
|
||||
{
|
||||
await ModalService.Launch<FullScreenModal>(parameters => { parameters["Parent"] = this; }, size: "max-w-none");
|
||||
}
|
||||
|
||||
private async Task TogglePause()
|
||||
{
|
||||
IsPaused = !IsPaused;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
if (IsPaused)
|
||||
return;
|
||||
|
||||
var queueContent = string.Concat(WriteQueue);
|
||||
WriteQueue.Clear();
|
||||
|
||||
await HandleWrite(queueContent);
|
||||
}
|
||||
|
||||
private async Task SubmitCommand()
|
||||
{
|
||||
CommandHistory.Add(CommandInput);
|
||||
|
||||
if (OnCommand != null)
|
||||
await OnCommand.Invoke(CommandInput);
|
||||
|
||||
CommandIndex = -1;
|
||||
CommandInput = "";
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task HandleKey(KeyboardEventArgs keyboard)
|
||||
{
|
||||
switch (keyboard.Code)
|
||||
{
|
||||
case "Enter":
|
||||
await SubmitCommand();
|
||||
break;
|
||||
|
||||
case "ArrowUp" or "ArrowDown":
|
||||
{
|
||||
var highestIndex = CommandHistory.Count - 1;
|
||||
|
||||
if (CommandIndex == -1)
|
||||
CommandIndex = highestIndex;
|
||||
else
|
||||
{
|
||||
if (keyboard.Code is "ArrowUp")
|
||||
CommandIndex++;
|
||||
else if (keyboard.Code is "ArrowDown")
|
||||
CommandIndex--;
|
||||
}
|
||||
|
||||
if (CommandIndex > highestIndex)
|
||||
CommandIndex = highestIndex;
|
||||
|
||||
if (CommandIndex < 0)
|
||||
CommandIndex = 0;
|
||||
|
||||
if (CommandIndex <= highestIndex || CommandHistory.Count > 0)
|
||||
CommandInput = CommandHistory[CommandIndex];
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await Terminal.DisposeAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user