Files
Moonlight/Moonlight/Features/Servers/UI/Layouts/UserLayout.razor
2024-04-12 10:41:54 +02:00

348 lines
12 KiB
Plaintext

@using Moonlight.Features.Servers.Entities
@using Moonlight.Features.Servers.Models.Abstractions
@using Moonlight.Features.Servers.Models.Enums
@using Moonlight.Features.Servers.Services
@using Moonlight.Features.Servers.UI.Components
@using MoonCore.Abstractions
@using MoonCore.Helpers
@using MoonCoreUI.Services
@using Microsoft.EntityFrameworkCore
@using Moonlight.Features.Servers.Helpers
@using Moonlight.Features.Servers.UI.UserViews
@using System.Net.Sockets
@using System.Net.WebSockets
@inject Repository<Server> ServerRepository
@inject ServerService ServerService
@inject ToastService ToastService
@implements IDisposable
<LazyLoader Load="Load" ShowAsCard="true">
<div class="card card-body pb-5 pt-5">
<div class="d-flex justify-content-between">
<div class="d-flex flex-row">
<div class="d-flex flex-column ms-3">
<span class="fw-bold text-gray-900 fs-3">
@Server.Name
</span>
<span class="text-gray-500 pt-2 fw-semibold fs-6">
@(Server.Image.Name)
</span>
</div>
<div class="vr mx-4"></div>
<div class="d-flex flex-column">
<div class="text-gray-900 fs-4">
@{
var color = ServerUtilsHelper.GetColorFromState(Console.State);
}
<i class="bx bx-sm bxs-circle text-@(color) @(Console.State != ServerState.Offline ? $"pulse pulse-{color}" : "") align-middle"></i>
<span class="align-middle">
@(Console.State)
<span class="text-muted">(@(Formatter.FormatUptime(DateTime.UtcNow - Console.LastStateChangeTimestamp)))</span>
</span>
</div>
<div class="text-gray-800 pt-3 fw-semibold fs-5 row">
<div class="col-auto">
<span>
<i class="bx bx-sm bx-globe align-middle text-info"></i>
<span class="align-middle">@(Server.Node.Fqdn):@(Server.MainAllocation.Port)</span>
</span>
</div>
<div class="col-auto">
<span>
<i class="bx bx-sm bx-globe align-middle text-info"></i>
<span class="align-middle">188.75.252.37:10324</span>
</span>
</div>
</div>
</div>
</div>
<div>
<div class="mt-2">
@if (Console.State == ServerState.Offline)
{
<WButton
OnClick="Start"
CssClasses="btn btn-light-success btn-icon me-1 my-1">
<i class="bx bx-sm bx-play"></i>
</WButton>
}
else
{
<button type="button" class="btn btn-light-success btn-icon me-1 my-1 disabled" disabled="">
<i class="bx bx-sm bx-play"></i>
</button>
}
@if (Console.State == ServerState.Offline || Console.State == ServerState.Installing)
{
<button class="btn btn-light-warning btn-icon me-1 my-1 disabled" disabled="">
<i class="bx bx-sm bx-power-off"></i>
</button>
}
else
{
<WButton
OnClick="Stop"
CssClasses="btn btn-light-warning btn-icon me-1 my-1">
<i class="bx bx-sm bx-power-off"></i>
</WButton>
}
@if (Console.State == ServerState.Offline || Console.State == ServerState.Installing)
{
<button class="btn btn-light-danger btn-icon me-1 my-1 disabled" disabled="">
<i class="bx bx-sm bx-bomb"></i>
</button>
}
else
{
<WButton
OnClick="Kill"
CssClasses="btn btn-light-danger btn-icon me-1 my-1">
<i class="bx bx-sm bx-bomb"></i>
</WButton>
}
</div>
</div>
</div>
</div>
<ServerNavigation Index="@GetIndex()" ServerId="@Id"/>
<div class="mt-3">
@if (IsInstalling)
{
<div class="card card-body bg-black p-3">
<Terminal @ref="InstallTerminal" EnableClipboard="false"/>
</div>
}
else if (IsConsoleDisconnected)
{
<IconAlert Title="Connection to server lost" Color="danger" Icon="bx-error">
We lost the connection to the server. Please refresh the page in order to retry. If this error persists please contact the support
</IconAlert>
}
else if (IsNodeOffline)
{
<IconAlert Title="Unable to establish a connection to the server" Color="danger" Icon="bx-error">
We are unable to establish a connection to the server. Please refresh the page in order to retry. If this error persists please contact the support
</IconAlert>
}
else if (IsNodeNotReady)
{
<IconAlert Title="The node is still booting" Color="warning" Icon="bx-server">
The node this server is on is still booting. Please refresh the page in order to retry. If this error persists please contact the support
</IconAlert>
}
else
{
<CascadingValue Value="Server">
<CascadingValue Value="Console">
<SmartRouter Route="@Route">
<Route Path="/">
<Console/>
</Route>
<Route Path="/files">
<Files/>
</Route>
<Route Path="/stats">
<Stats/>
</Route>
<Route Path="/backups">
<Backups/>
</Route>
<Route Path="/network">
<Network/>
</Route>
<Route Path="/schedules">
<Schedules/>
</Route>
<Route Path="/variables">
<Variables/>
</Route>
<Route Path="/reset">
<Reset/>
</Route>
</SmartRouter>
</CascadingValue>
</CascadingValue>
}
</div>
</LazyLoader>
@code
{
[Parameter] public string? Route { get; set; }
[Parameter] public int Id { get; set; }
private Server Server;
private ServerConsole Console;
private Terminal? InstallTerminal;
private bool IsInstalling = false;
private bool IsConsoleDisconnected = false;
private bool IsNodeOffline = false;
private bool IsNodeNotReady = false;
private Timer UpdateTimer;
private async Task Load(LazyLoader lazyLoader)
{
await lazyLoader.SetText("Loading server information");
Server = ServerRepository
.Get()
.Include(x => x.Image)
.Include(x => x.Node)
.Include(x => x.Variables)
.Include(x => x.Allocations)
.Include(x => x.Network)
.Include(x => x.MainAllocation)
.Include(x => x.Owner)
.First(x => x.Id == Id);
await lazyLoader.SetText("Establishing a connection to the server");
// Create console wrapper
Console = new ServerConsole(Server);
// Configure
Console.OnStateChange += async state =>
{
// General rerender to update the state text in the ui
// NOTE: Obsolete because of the update timer
//await InvokeAsync(StateHasChanged);
// Change from offline to installing
// This will trigger the initialisation of the install view
if (state == ServerState.Installing && !IsInstalling)
{
IsInstalling = true;
// After this call, we should have access to the install terminal reference
await InvokeAsync(StateHasChanged);
Console.OnNewMessage += OnInstallConsoleMessage;
}
// Change from installing to offline
// This will trigger the destruction of the install view
else if (state == ServerState.Offline && IsInstalling)
{
IsInstalling = false;
Console.OnNewMessage -= OnInstallConsoleMessage;
// After this call, the install terminal will disappear
await InvokeAsync(StateHasChanged);
await ToastService.Info("Server installation complete");
}
};
Console.OnDisconnected += async () =>
{
IsConsoleDisconnected = true;
await InvokeAsync(StateHasChanged);
};
try
{
await Console.Connect();
}
catch (WebSocketException e)
{
if (e.Message.Contains("503"))
{
IsNodeNotReady = true;
await InvokeAsync(StateHasChanged);
return;
}
// We want to check if its an connection error, if yes we want to show the user that its an error with the connection
// If not we proceed with the throwing for the soft error handler.
if (e.InnerException is not HttpRequestException httpRequestException)
throw;
if (httpRequestException.InnerException is not SocketException socketException)
throw;
Logger.Warn($"Unable to access the node's websocket endpoint: {socketException.Message}");
// Change the ui and...
IsNodeOffline = true;
await InvokeAsync(StateHasChanged);
// ... stop loading more data
return;
}
UpdateTimer = new Timer(async _ => { await InvokeAsync(StateHasChanged); }, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
}
private async Task OnInstallConsoleMessage(string message)
{
if (InstallTerminal != null)
await InstallTerminal.WriteLine(message);
}
private async Task Start() => await ServerService.Console.SendAction(Server, PowerAction.Start);
private async Task Stop() => await ServerService.Console.SendAction(Server, PowerAction.Stop);
private async Task Kill() => await ServerService.Console.SendAction(Server, PowerAction.Kill);
public async void Dispose()
{
if (UpdateTimer != null)
await UpdateTimer.DisposeAsync();
if (Console != null)
await Console.Close();
}
private int GetIndex()
{
if (string.IsNullOrEmpty(Route))
return 0;
var route = "/" + (Route ?? "");
switch (route)
{
case "/files":
return 1;
case "/stats":
return 5;
case "/backups":
return 6;
case "/network":
return 2;
case "/schedules":
return 7;
case "/variables":
return 3;
case "/reset":
return 4;
default:
return 0;
}
}
}