Refactored ui. Improved console experience. Added command endpoint
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using MoonCore.Attributes;
|
||||
using MoonCore.Helpers;
|
||||
using MoonCore.Models;
|
||||
using MoonlightServers.Shared.Http.Requests.Client.Servers;
|
||||
using MoonlightServers.Shared.Http.Requests.Client.Servers.Variables;
|
||||
using MoonlightServers.Shared.Http.Responses.Client.Servers;
|
||||
using MoonlightServers.Shared.Http.Responses.Client.Servers.Variables;
|
||||
@@ -59,6 +60,17 @@ public class ServerService
|
||||
);
|
||||
}
|
||||
|
||||
public async Task RunCommand(int serverId, string command)
|
||||
{
|
||||
await HttpApiClient.Post(
|
||||
$"api/client/servers/{serverId}/command",
|
||||
new ServerCommandRequest()
|
||||
{
|
||||
Command = command
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<ServerWebSocketResponse> GetWebSocket(int serverId)
|
||||
{
|
||||
return await HttpApiClient.GetJson<ServerWebSocketResponse>(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
@using MoonCore.Helpers
|
||||
@using Moonlight.Shared.Http.Responses.Admin.Users
|
||||
@using MoonlightServers.Shared.Http.Requests.Admin.Servers
|
||||
@using MoonlightServers.Frontend.UI.Components.Servers.CreateServerPartials
|
||||
@using MoonlightServers.Frontend.UI.Components.Servers.CreatePartials
|
||||
@using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations
|
||||
@using MoonlightServers.Shared.Http.Responses.Admin.Nodes
|
||||
@using MoonlightServers.Shared.Http.Responses.Admin.Stars
|
||||
@@ -18,11 +18,11 @@
|
||||
|
||||
<PageHeader Title="Create Server">
|
||||
<a href="/admin/servers/all" class="btn btn-secondary">
|
||||
<i class="icon-chevron-left mr-1"></i>
|
||||
<i class="icon-chevron-left"></i>
|
||||
Back
|
||||
</a>
|
||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||
<i class="icon-check mr-1"></i>
|
||||
<i class="icon-check"></i>
|
||||
Create
|
||||
</WButton>
|
||||
</PageHeader>
|
||||
@@ -31,16 +31,16 @@
|
||||
<HandleForm @ref="Form" Model="Request" OnValidSubmit="OnSubmit">
|
||||
<Tabs>
|
||||
<Tab Name="General">
|
||||
<GeneralServerCreate Request="Request" Parent="this" />
|
||||
<General Request="Request" Parent="this" />
|
||||
</Tab>
|
||||
<Tab Name="Allocations">
|
||||
<AllocationsServerCreate Request="Request" Parent="this" />
|
||||
<Allocations Request="Request" Parent="this" />
|
||||
</Tab>
|
||||
<Tab Name="Variables">
|
||||
<VariablesServerCreate Request="Request" Parent="this" />
|
||||
<Variables Request="Request" Parent="this" />
|
||||
</Tab>
|
||||
<Tab Name="Advanced">
|
||||
<AdvancedServerCreate Request="Request" />
|
||||
<Advanced Request="Request" />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</HandleForm>
|
||||
|
||||
@@ -33,7 +33,13 @@
|
||||
<Pagination TItem="ServerResponse" ItemSource="LoadData" />
|
||||
|
||||
<DataTableColumn TItem="ServerResponse" Field="@(x => x.Id)" Name="Id"/>
|
||||
<DataTableColumn TItem="ServerResponse" Field="@(x => x.Name)" Name="Name"/>
|
||||
<DataTableColumn TItem="ServerResponse" Field="@(x => x.Name)" Name="Name">
|
||||
<ColumnTemplate>
|
||||
<a class="text-primary" href="/admin/servers/all/update/@context.Id">
|
||||
@context.Name
|
||||
</a>
|
||||
</ColumnTemplate>
|
||||
</DataTableColumn>
|
||||
<DataTableColumn TItem="ServerResponse" Field="@(x => x.NodeId)" Name="Node">
|
||||
<ColumnTemplate>
|
||||
@{
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
@using Moonlight.Shared.Http.Responses.Admin.Users
|
||||
@using MoonlightServers.Shared.Http.Requests.Admin.Servers
|
||||
@using MoonlightServers.Shared.Http.Responses.Admin.Servers
|
||||
@using MoonlightServers.Frontend.UI.Components.Servers.UpdateServerPartials
|
||||
@using MoonlightServers.Frontend.UI.Components.Servers.UpdatePartials
|
||||
@using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations
|
||||
|
||||
@inject HttpApiClient ApiClient
|
||||
@@ -18,11 +18,11 @@
|
||||
<LazyLoader Load="Load">
|
||||
<PageHeader Title="Update Server">
|
||||
<a href="/admin/servers/all" class="btn btn-secondary">
|
||||
<i class="icon-chevron-left mr-1"></i>
|
||||
<i class="icon-chevron-left"></i>
|
||||
Back
|
||||
</a>
|
||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||
<i class="icon-check mr-1"></i>
|
||||
<i class="icon-check"></i>
|
||||
Update
|
||||
</WButton>
|
||||
</PageHeader>
|
||||
@@ -31,16 +31,16 @@
|
||||
<HandleForm @ref="Form" Model="Request" OnValidSubmit="OnSubmit">
|
||||
<Tabs>
|
||||
<Tab Name="General">
|
||||
<GeneralServerUpdate Request="Request" Owner="Owner"/>
|
||||
<General Request="Request" Parent="this"/>
|
||||
</Tab>
|
||||
<Tab Name="Allocations">
|
||||
<AllocationsServerUpdate Request="Request" Server="Server" Allocations="Allocations"/>
|
||||
<Allocations Request="Request" Server="Server" Parent="this"/>
|
||||
</Tab>
|
||||
<Tab Name="Variables">
|
||||
<VariablesServerUpdate Request="Request" Server="Server"/>
|
||||
<Variables Request="Request" Server="Server"/>
|
||||
</Tab>
|
||||
<Tab Name="Advanced">
|
||||
<AdvancedServerUpdate Request="Request"/>
|
||||
<Advanced Request="Request"/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</HandleForm>
|
||||
@@ -55,8 +55,8 @@
|
||||
private UpdateServerRequest Request;
|
||||
private ServerResponse Server;
|
||||
|
||||
private List<NodeAllocationResponse> Allocations = new();
|
||||
private UserResponse Owner;
|
||||
public List<NodeAllocationResponse> Allocations = new();
|
||||
public UserResponse Owner;
|
||||
|
||||
private async Task Load(LazyLoader _)
|
||||
{
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
|
||||
<PageHeader Title="Create Node">
|
||||
<a href="/admin/servers/nodes" class="btn btn-secondary">
|
||||
<i class="icon-chevron-left mr-1"></i>
|
||||
<i class="icon-chevron-left"></i>
|
||||
Back
|
||||
</a>
|
||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||
<i class="icon-check mr-1"></i>
|
||||
<i class="icon-check"></i>
|
||||
Create
|
||||
</WButton>
|
||||
</PageHeader>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@using MoonCore.Helpers
|
||||
@using MoonlightServers.Shared.Http.Requests.Admin.Nodes
|
||||
@using MoonlightServers.Shared.Http.Responses.Admin.Nodes
|
||||
@using MoonlightServers.Frontend.UI.Components.Nodes.UpdateNodePartials
|
||||
@using MoonlightServers.Frontend.UI.Components.Nodes.UpdatePartials
|
||||
|
||||
@inject HttpApiClient ApiClient
|
||||
@inject NavigationManager Navigation
|
||||
@@ -16,11 +16,11 @@
|
||||
<LazyLoader Load="Load">
|
||||
<PageHeader Title="@Node.Name">
|
||||
<a href="/admin/servers/nodes" class="btn btn-secondary">
|
||||
<i class="icon-chevron-left mr-1"></i>
|
||||
<i class="icon-chevron-left"></i>
|
||||
Back
|
||||
</a>
|
||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||
<i class="icon-check mr-1"></i>
|
||||
<i class="icon-check"></i>
|
||||
Update
|
||||
</WButton>
|
||||
</PageHeader>
|
||||
@@ -30,19 +30,19 @@
|
||||
|
||||
<Tabs>
|
||||
<Tab Name="Overview">
|
||||
<OverviewNodeUpdate Node="Node" />
|
||||
<Overview Node="Node" />
|
||||
</Tab>
|
||||
|
||||
<Tab Name="Settings">
|
||||
<GeneralNodeUpdate Request="Request"/>
|
||||
<General Request="Request"/>
|
||||
</Tab>
|
||||
|
||||
<Tab Name="Allocations">
|
||||
<AllocationsNodeUpdate Node="Node"/>
|
||||
<Allocations Node="Node"/>
|
||||
</Tab>
|
||||
|
||||
<Tab Name="Advanced Settings">
|
||||
<AdvancedNodeUpdate Request="Request"/>
|
||||
<Advanced Request="Request"/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
|
||||
<PageHeader Title="Create Star">
|
||||
<a href="/admin/servers/stars" class="btn btn-secondary">
|
||||
<i class="icon-chevron-left mr-1"></i>
|
||||
<i class="icon-chevron-left"></i>
|
||||
Back
|
||||
</a>
|
||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||
<i class="icon-check mr-1"></i>
|
||||
<i class="icon-check"></i>
|
||||
Create
|
||||
</WButton>
|
||||
</PageHeader>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<PageHeader Title="Stars">
|
||||
<InputFile id="import-file" hidden="" multiple OnChange="OnImportFiles"/>
|
||||
<label for="import-file" class="btn btn-accent cursor-pointer">
|
||||
<i class="icon-file-up mr-2"></i>
|
||||
<i class="icon-file-up"></i>
|
||||
Import
|
||||
</label>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@using MoonCore.Helpers
|
||||
@using MoonlightServers.Shared.Http.Requests.Admin.Stars
|
||||
@using MoonlightServers.Shared.Http.Responses.Admin.Stars
|
||||
@using MoonlightServers.Frontend.UI.Components.Stars.UpdateStarPartials
|
||||
@using MoonlightServers.Frontend.UI.Components.Stars.UpdatePartials
|
||||
|
||||
@inject HttpApiClient ApiClient
|
||||
@inject NavigationManager Navigation
|
||||
@@ -16,11 +16,11 @@
|
||||
<LazyLoader Load="Load">
|
||||
<PageHeader Title="Update Star">
|
||||
<a href="/admin/servers/stars" class="btn btn-secondary">
|
||||
<i class="icon-chevron-left mr-1"></i>
|
||||
<i class="icon-chevron-left"></i>
|
||||
Back
|
||||
</a>
|
||||
<WButton OnClick="_ => Form.Submit()" CssClasses="btn btn-primary">
|
||||
<i class="icon-check mr-1"></i>
|
||||
<i class="icon-check"></i>
|
||||
Update
|
||||
</WButton>
|
||||
</PageHeader>
|
||||
@@ -30,31 +30,31 @@
|
||||
|
||||
<Tabs>
|
||||
<Tab Name="General">
|
||||
<GeneralStarUpdate Request="Request" />
|
||||
<General Request="Request" />
|
||||
</Tab>
|
||||
|
||||
<Tab Name="Start, Stop & Status">
|
||||
<StartStopStatusStarUpdate Request="Request" />
|
||||
<StartStopStatus Request="Request" />
|
||||
</Tab>
|
||||
|
||||
<Tab Name="Parse Configuration">
|
||||
<ParseConfigStarUpdate Request="Request" />
|
||||
<ParseConfig Request="Request" />
|
||||
</Tab>
|
||||
|
||||
<Tab Name="Installation">
|
||||
<InstallationStarUpdate Request="Request" />
|
||||
<Installation Request="Request" />
|
||||
</Tab>
|
||||
|
||||
<Tab Name="Variables">
|
||||
<VariablesStarUpdate Star="Detail" />
|
||||
<Variables Star="Detail" />
|
||||
</Tab>
|
||||
|
||||
<Tab Name="Docker Images">
|
||||
<DockerImageStarUpdate Star="Detail" />
|
||||
<DockerImage Star="Detail" />
|
||||
</Tab>
|
||||
|
||||
<Tab Name="Miscellaneous">
|
||||
<MiscStarUpdate Star="Detail" Request="Request" />
|
||||
<Misc Star="Detail" Request="Request" />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
@@ -64,14 +64,14 @@
|
||||
@if (State == ServerState.Offline)
|
||||
{
|
||||
<WButton CssClasses="btn btn-success" OnClick="_ => Start()">
|
||||
<i class="icon-play me-1 align-middle"></i>
|
||||
<i class="icon-play align-middle"></i>
|
||||
<span class="align-middle">Start</span>
|
||||
</WButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-success" disabled="disabled">
|
||||
<i class="icon-play me-1 align-middle"></i>
|
||||
<i class="icon-play align-middle"></i>
|
||||
<span class="align-middle">Start</span>
|
||||
</button>
|
||||
}
|
||||
@@ -79,14 +79,14 @@
|
||||
@if (State == ServerState.Online)
|
||||
{
|
||||
<button type="button" class="btn btn-primary">
|
||||
<i class="icon-rotate-ccw me-1 align-middle"></i>
|
||||
<i class="icon-rotate-ccw align-middle"></i>
|
||||
<span class="align-middle">Restart</span>
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-primary" disabled="disabled">
|
||||
<i class="icon-rotate-ccw me-1 align-middle"></i>
|
||||
<i class="icon-rotate-ccw align-middle"></i>
|
||||
<span class="align-middle">Restart</span>
|
||||
</button>
|
||||
}
|
||||
@@ -96,14 +96,14 @@
|
||||
if (State == ServerState.Stopping)
|
||||
{
|
||||
<WButton CssClasses="btn btn-error" OnClick="_ => Kill()">
|
||||
<i class="icon-bomb me-1 align-middle"></i>
|
||||
<i class="icon-bomb align-middle"></i>
|
||||
<span class="align-middle">Kill</span>
|
||||
</WButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<WButton CssClasses="btn btn-error" OnClick="_ => Stop()">
|
||||
<i class="icon-squircle me-1 align-middle"></i>
|
||||
<i class="icon-squircle align-middle"></i>
|
||||
<span class="align-middle">Stop</span>
|
||||
</WButton>
|
||||
}
|
||||
@@ -111,7 +111,7 @@
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-error" disabled="disabled">
|
||||
<i class="icon-squircle me-1 align-middle"></i>
|
||||
<i class="icon-squircle align-middle"></i>
|
||||
<span class="align-middle">Stop</span>
|
||||
</button>
|
||||
}
|
||||
@@ -119,17 +119,17 @@
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-success" disabled="disabled">
|
||||
<i class="icon-play me-1 align-middle"></i>
|
||||
<i class="icon-play align-middle"></i>
|
||||
<span class="align-middle">Start</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-primary" disabled="disabled">
|
||||
<i class="icon-rotate-ccw me-1 align-middle"></i>
|
||||
<i class="icon-rotate-ccw align-middle"></i>
|
||||
<span class="align-middle">Restart</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-error" disabled="disabled">
|
||||
<i class="icon-squircle me-1 align-middle"></i>
|
||||
<i class="icon-squircle align-middle"></i>
|
||||
<span class="align-middle">Stop</span>
|
||||
</button>
|
||||
}
|
||||
@@ -138,28 +138,20 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<ul class="flex flex-wrap -m-1">
|
||||
|
||||
<nav class="tabs space-x-2 overflow-x-auto" aria-label="Tabs" role="tablist" aria-orientation="horizontal">
|
||||
@foreach (var tab in Tabs)
|
||||
{
|
||||
<li class="m-1">
|
||||
@if (tab == CurrentTab)
|
||||
{
|
||||
<a href="/servers/@(ServerId)/@(tab.Path)"
|
||||
class="inline-flex items-center justify-center text-sm font-medium leading-5 rounded-full px-3 py-1 border border-transparent shadow-sm bg-gray-100 text-gray-800 transition">
|
||||
@tab.Name
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="/servers/@(ServerId)/@(tab.Path)" @onclick:preventDefault
|
||||
@onclick="() => SwitchTab(tab)"
|
||||
class="inline-flex items-center justify-center text-sm font-medium leading-5 rounded-full px-3 py-1 border border-gray-700/60 hover:border-gray-600 shadow-sm bg-gray-800 text-base-content/60 transition">
|
||||
@tab.Name
|
||||
</a>
|
||||
}
|
||||
</li>
|
||||
<a href="/servers/@(ServerId)/@(tab.Path)"
|
||||
class="btn btn-text active-tab:bg-primary active-tab:text-primary-content hover:text-primary hover:bg-primary/20 bg-base-150 text-sm py-0.5 px-3 rounded-xl @(tab == CurrentTab ? "active" : "")"
|
||||
data-tab="na"
|
||||
role="tab"
|
||||
@onclick:preventDefault
|
||||
@onclick="() => SwitchTab(tab)">
|
||||
@tab.Name
|
||||
</a>
|
||||
}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="mt-5">
|
||||
@if (CurrentTab == null)
|
||||
@@ -201,6 +193,8 @@
|
||||
|
||||
private HubConnection? HubConnection;
|
||||
|
||||
public ConcurrentList<string> CommandHistory = new();
|
||||
|
||||
private async Task Load(LazyLoader _)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user