Files
Moonlight/Moonlight/Features/Servers/UI/Layouts/UserLayout.razor
2024-05-19 15:20:24 +02:00

403 lines
14 KiB
Plaintext

@using Moonlight.Features.Servers.Entities
@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
@using MoonCore.Exceptions
@using Moonlight.Features.Servers.Configuration
@using MoonCore.Services
@using Moonlight.Core.Services
@inject Repository<Server> ServerRepository
@inject ServerService ServerService
@inject ToastService ToastService
@inject AlertService AlertService
@inject IdentityService IdentityService
@inject ConfigService<ServersConfiguration> ConfigService
@implements IDisposable
<LazyLoader Load="Load" ShowAsCard="true">
<div class="card card-body pb-5 pt-5">
<div>
<div class="row px-2">
<div class="col-12 col-sm-3">
<span class="fw-bold text-gray-900 fs-3 d-block">
@Server.Name
</span>
<span class="text-gray-500 pt-2 fw-semibold fs-6 d-block">
@(Server.Image.Name)
</span>
</div>
<div class="vr p-0 mx-4 d-none d-sm-block"></div>
<hr class="col-sm my-4 d-block d-sm-none"/>
<div class="col-12 col-sm">
<div class="row">
<div class="col">
<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 class="col-auto">
<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>
</div>
</div>
</div>
<ServerNavigation Index="@GetIndex()" ServerId="@Id"/>
<div class="mt-3">
@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 if (IsInstalling)
{
<div class="card card-body bg-black p-3">
<Terminal @ref="InstallTerminal" EnableClipboard="false"/>
</div>
}
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);
if (Server.Owner.Id != IdentityService.CurrentUser.Id && IdentityService.CurrentUser.Permissions < 5000)
{
Server = null!;
return;
}
await lazyLoader.SetText("Establishing a connection to the server");
// Create console wrapper
Console = new ServerConsole(Server);
// Configure
Console.OnStateChange += async state => await HandleStateChange(state);
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));
var state = await ServerService.GetState(Server);
await HandleStateChange(state);
}
private async Task HandleStateChange(ServerState 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");
}
}
private async Task OnInstallConsoleMessage(string message)
{
if (InstallTerminal != null)
await InstallTerminal.WriteLine(message);
}
private async Task Start() => await SendSignalHandled(PowerAction.Start);
private async Task Stop() => await SendSignalHandled(PowerAction.Stop);
private async Task Kill()
{
if (!ConfigService.Get().DisableServerKillWarning)
{
if (!await AlertService.YesNo("Do you really want to kill the server? This can result in data loss or corrupted server files"))
return;
}
await SendSignalHandled(PowerAction.Kill);
}
private async Task SendSignalHandled(PowerAction action)
{
try
{
await ServerService.Console.SendAction(Server, action);
}
catch (DisplayException)
{
throw;
}
catch (Exception e)
{
Logger.Warn($"An error occured while sending power action {action} to server {Server.Id}:");
Logger.Warn(e);
await ToastService.Danger("An error occured while sending power action to server. Check the console for more information");
}
}
public async void Dispose()
{
if (UpdateTimer != null)
await UpdateTimer.DisposeAsync();
if (Console != null)
{
await Console.Close();
Console.Dispose();
}
}
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;
}
}
}