355 lines
15 KiB
Plaintext
355 lines
15 KiB
Plaintext
@page "/servers"
|
|
@using Microsoft.EntityFrameworkCore
|
|
@using Moonlight.App.Database.Entities
|
|
@using Moonlight.App.Helpers
|
|
@using Moonlight.App.Models.Misc
|
|
@using Moonlight.App.Repositories
|
|
@using Moonlight.App.Services
|
|
@using Newtonsoft.Json
|
|
|
|
@inject Repository<Server> ServerRepository
|
|
@inject Repository<User> UserRepository
|
|
@inject SmartTranslateService SmartTranslateService
|
|
@inject IServiceScopeFactory ServiceScopeFactory
|
|
@inject IJSRuntime JsRuntime
|
|
|
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
|
<div class="mx-auto">
|
|
<div class="card card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<span class="badge badge-primary badge-lg px-5 me-4">Beta</span>
|
|
@if (EditMode)
|
|
{
|
|
<div>
|
|
<WButton Text="@(SmartTranslateService.Translate("New group"))"
|
|
WorkingText=""
|
|
CssClasses="btn-primary me-3"
|
|
OnClick="AddGroup">
|
|
</WButton>
|
|
<WButton Text="@(SmartTranslateService.Translate("Finish editing layout"))"
|
|
CssClasses="btn-secondary"
|
|
OnClick="async () => await SetEditMode(false)">
|
|
</WButton>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<WButton Text="@(SmartTranslateService.Translate("Edit layout"))"
|
|
CssClasses="btn-secondary"
|
|
OnClick="async () => await SetEditMode(true)">
|
|
</WButton>
|
|
}
|
|
</div>
|
|
</div>
|
|
@foreach (var group in ServerGroups)
|
|
{
|
|
<div class="accordion my-3" id="serverListGroup@(group.GetHashCode())">
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header" id="serverListGroup-header@(group.GetHashCode())">
|
|
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#serverListGroup-body@(group.GetHashCode())" aria-expanded="false" aria-controls="serverListGroup-body@(group.GetHashCode())">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
@if (EditMode)
|
|
{
|
|
<input @bind="group.Name" class="form-control"/>
|
|
}
|
|
else
|
|
{
|
|
if (string.IsNullOrEmpty(group.Name))
|
|
{
|
|
<TL>Unsorted servers</TL>
|
|
}
|
|
else
|
|
{
|
|
<span>@(group.Name)</span>
|
|
}
|
|
}
|
|
</div>
|
|
<div>
|
|
@if (EditMode)
|
|
{
|
|
<WButton Text="@(SmartTranslateService.Translate("Remove group"))"
|
|
WorkingText=""
|
|
CssClasses="btn-danger"
|
|
OnClick="async () => await RemoveGroup(group)">
|
|
</WButton>
|
|
}
|
|
</div>
|
|
</div>
|
|
</button>
|
|
</h2>
|
|
<div id="serverListGroup-body@(group.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="serverListGroup-header@(group.GetHashCode())" data-bs-parent="#serverListGroup">
|
|
<div class="accordion-body">
|
|
<div class="row min-h-200px draggable-zone" ml-server-group="@(group.Name)">
|
|
@foreach (var id in group.Servers)
|
|
{
|
|
var server = AllServers.First(x => x.Id.ToString() == id);
|
|
|
|
<div class="col-12 col-md-3 p-3 draggable" ml-server-id="@(server.Id)">
|
|
@if (EditMode)
|
|
{
|
|
<div class="card bg-secondary">
|
|
<div class="card-header">
|
|
<div class="card-title">
|
|
<span class="card-label">@(server.Name)</span>
|
|
</div>
|
|
<div class="card-toolbar">
|
|
<a href="#" class="btn btn-icon btn-sm btn-hover-light-primary draggable-handle">
|
|
<i class="bx bx-md bx-move"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<TL>Hidden in edit mode</TL>
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<a class="invisible-a" href="/server/@(server.Uuid)">
|
|
<div class="card bg-secondary">
|
|
<div class="card-header">
|
|
<div class="card-title">
|
|
<span class="card-label">@(server.Name)</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<span class="card-text fs-6">
|
|
@(Math.Round(server.Memory / 1024D, 2)) GB / @(Math.Round(server.Disk / 1024D, 2)) GB / @(server.Node.Name) <span class="text-gray-700">- @(server.Image.Name)</span>
|
|
</span>
|
|
<div class="card-text my-1 fs-6 fw-bold @(User.StreamerMode ? "blur" : "")">
|
|
@(server.Node.Fqdn):@(server.MainAllocation.Port)
|
|
</div>
|
|
<div class="card-text fs-6">
|
|
@if (StatusCache.ContainsKey(server))
|
|
{
|
|
var status = StatusCache[server];
|
|
|
|
switch (status)
|
|
{
|
|
case "offline":
|
|
<span class="text-danger">
|
|
<TL>Offline</TL>
|
|
</span>
|
|
break;
|
|
case "stopping":
|
|
<span class="text-warning">
|
|
<TL>Stopping</TL>
|
|
</span>
|
|
break;
|
|
case "starting":
|
|
<span class="text-warning">
|
|
<TL>Starting</TL>
|
|
</span>
|
|
break;
|
|
case "running":
|
|
<span class="text-success">
|
|
<TL>Running</TL>
|
|
</span>
|
|
break;
|
|
case "failed":
|
|
<span class="text-gray-400">
|
|
<TL>Failed</TL>
|
|
</span>
|
|
break;
|
|
default:
|
|
<span class="text-danger">
|
|
<TL>Offline</TL>
|
|
</span>
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
<span class="text-gray-400">
|
|
<TL>Loading</TL>
|
|
</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</LazyLoader>
|
|
|
|
@code
|
|
{
|
|
[CascadingParameter]
|
|
public User User { get; set; }
|
|
|
|
private Server[] AllServers;
|
|
private LazyLoader LazyLoader;
|
|
private readonly Dictionary<Server, string> StatusCache = new();
|
|
|
|
private List<ServerGroup> ServerGroups = new();
|
|
private bool EditMode = false;
|
|
|
|
private async Task Load(LazyLoader arg)
|
|
{
|
|
AllServers = ServerRepository
|
|
.Get()
|
|
.Include(x => x.Owner)
|
|
.Include(x => x.MainAllocation)
|
|
.Include(x => x.Node)
|
|
.Include(x => x.Image)
|
|
.Where(x => x.Owner.Id == User.Id)
|
|
.OrderBy(x => x.Name)
|
|
.ToArray();
|
|
|
|
|
|
if (string.IsNullOrEmpty(User.ServerListLayoutJson))
|
|
{
|
|
ServerGroups.Add(new()
|
|
{
|
|
Name = "",
|
|
Servers = AllServers.Select(x => x.Id.ToString()).ToList()
|
|
});
|
|
}
|
|
else
|
|
{
|
|
ServerGroups = (JsonConvert.DeserializeObject<ServerGroup[]>(
|
|
User.ServerListLayoutJson) ?? Array.Empty<ServerGroup>()).ToList();
|
|
}
|
|
|
|
foreach (var server in AllServers)
|
|
{
|
|
Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
using var scope = ServiceScopeFactory.CreateScope();
|
|
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
|
|
|
AddStatus(server, (await serverService.GetDetails(server)).State);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
AddStatus(server, "failed");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private async Task AddGroup()
|
|
{
|
|
ServerGroups.Insert(0, new()
|
|
{
|
|
Name = "New group"
|
|
});
|
|
|
|
await InvokeAsync(StateHasChanged);
|
|
await JsRuntime.InvokeVoidAsync("moonlight.serverList.init");
|
|
}
|
|
|
|
private async Task RemoveGroup(ServerGroup group)
|
|
{
|
|
ServerGroups.Remove(group);
|
|
await EnsureAllServersInGroups();
|
|
|
|
await InvokeAsync(StateHasChanged);
|
|
|
|
await JsRuntime.InvokeVoidAsync("moonlight.serverList.init");
|
|
}
|
|
|
|
private async Task SetEditMode(bool toggle)
|
|
{
|
|
EditMode = toggle;
|
|
await InvokeAsync(StateHasChanged);
|
|
|
|
if (EditMode)
|
|
{
|
|
await EnsureAllServersInGroups();
|
|
await InvokeAsync(StateHasChanged);
|
|
|
|
await JsRuntime.InvokeVoidAsync("moonlight.serverList.init");
|
|
}
|
|
else
|
|
{
|
|
var json = JsonConvert.SerializeObject(await GetGroupsFromClient());
|
|
User.ServerListLayoutJson = json;
|
|
UserRepository.Update(User);
|
|
|
|
await LazyLoader.Reload();
|
|
}
|
|
}
|
|
|
|
private async Task<ServerGroup[]> GetGroupsFromClient()
|
|
{
|
|
var serverGroups = await JsRuntime.InvokeAsync<ServerGroup[]>("moonlight.serverList.getData");
|
|
|
|
// Check user data to prevent users from doing stupid stuff
|
|
foreach (var serverGroup in serverGroups)
|
|
{
|
|
if (serverGroup.Name.Length > 30)
|
|
{
|
|
Logger.Verbose("Server list group lenght too long");
|
|
return Array.Empty<ServerGroup>();
|
|
}
|
|
|
|
if (serverGroup.Servers.Any(x => AllServers.All(y => y.Id.ToString() != x)))
|
|
{
|
|
Logger.Verbose("User tried to add a server in his server list which he has no access to");
|
|
return Array.Empty<ServerGroup>();
|
|
}
|
|
}
|
|
|
|
return serverGroups;
|
|
}
|
|
|
|
private Task EnsureAllServersInGroups()
|
|
{
|
|
var presentInGroup = new List<Server>();
|
|
|
|
foreach (var group in ServerGroups)
|
|
{
|
|
foreach (var id in group.Servers)
|
|
presentInGroup.Add(AllServers.First(x => x.Id.ToString() == id));
|
|
}
|
|
|
|
var serversMissing = new List<Server>();
|
|
|
|
foreach (var server in AllServers)
|
|
{
|
|
if (presentInGroup.All(x => x.Id != server.Id))
|
|
serversMissing.Add(server);
|
|
}
|
|
|
|
if (serversMissing.Any())
|
|
{
|
|
var defaultGroup = ServerGroups.FirstOrDefault(x => x.Name == "");
|
|
|
|
if (defaultGroup == null)
|
|
{
|
|
defaultGroup = new ServerGroup()
|
|
{
|
|
Name = ""
|
|
};
|
|
|
|
ServerGroups.Add(defaultGroup);
|
|
}
|
|
|
|
foreach (var server in serversMissing)
|
|
defaultGroup.Servers.Add(server.Id.ToString());
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private void AddStatus(Server server, string status)
|
|
{
|
|
lock (StatusCache)
|
|
{
|
|
StatusCache.Add(server, status);
|
|
InvokeAsync(StateHasChanged);
|
|
}
|
|
}
|
|
} |