Merge branch 'main' into TOTP
This commit is contained in:
@@ -13,353 +13,204 @@ namespace Moonlight.App.Services;
|
|||||||
|
|
||||||
public class CleanupService
|
public class CleanupService
|
||||||
{
|
{
|
||||||
|
#region Stats
|
||||||
public DateTime StartedAt { get; private set; }
|
public DateTime StartedAt { get; private set; }
|
||||||
public DateTime CompletedAt { get; private set; }
|
public DateTime CompletedAt { get; private set; }
|
||||||
public int ServersCleaned { get; private set; }
|
public int ServersCleaned { get; private set; }
|
||||||
public int CleanupsPerformed { get; private set; }
|
public int CleanupsPerformed { get; private set; }
|
||||||
public int ServersRunning { get; private set; }
|
public int ServersRunning { get; private set; }
|
||||||
public bool IsRunning { get; private set; }
|
public bool IsRunning { get; private set; }
|
||||||
public bool Activated { get; set; }
|
#endregion
|
||||||
public int PercentProgress { get; private set; } = 100;
|
|
||||||
public string Status { get; private set; } = "N/A";
|
|
||||||
|
|
||||||
private Task PerformTask;
|
|
||||||
private readonly ConfigService ConfigService;
|
private readonly ConfigService ConfigService;
|
||||||
|
private readonly MessageService MessageService;
|
||||||
private readonly IServiceScopeFactory ServiceScopeFactory;
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
|
private readonly PeriodicTimer Timer;
|
||||||
|
|
||||||
private int RequiredCpu;
|
public CleanupService(
|
||||||
private long RequiredMemory;
|
ConfigService configService,
|
||||||
private int WaitTime;
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
|
MessageService messageService)
|
||||||
public EventHandler OnUpdated;
|
|
||||||
|
|
||||||
public CleanupService(ConfigService configService, IServiceScopeFactory serviceScopeFactory)
|
|
||||||
{
|
{
|
||||||
ServiceScopeFactory = serviceScopeFactory;
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
|
MessageService = messageService;
|
||||||
ConfigService = configService;
|
ConfigService = configService;
|
||||||
|
|
||||||
var config = configService.GetSection("Moonlight").GetSection("Cleanup");
|
|
||||||
|
|
||||||
RequiredCpu = config.GetValue<int>("Cpu");
|
|
||||||
RequiredMemory = config.GetValue<long>("Memory");
|
|
||||||
WaitTime = config.GetValue<int>("Wait");
|
|
||||||
|
|
||||||
if (!ConfigService.DebugMode)
|
|
||||||
Task.Run(Start);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Start()
|
|
||||||
{
|
|
||||||
StartedAt = DateTime.Now;
|
StartedAt = DateTime.Now;
|
||||||
CompletedAt = DateTime.Now;
|
CompletedAt = DateTime.Now;
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
Activated = true;
|
|
||||||
|
|
||||||
DoWaiting();
|
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup");
|
||||||
}
|
|
||||||
|
|
||||||
private async void DoWaiting()
|
if (!config.GetValue<bool>("Enable") || ConfigService.DebugMode)
|
||||||
{
|
{
|
||||||
while (true)
|
Logger.Info("Disabling cleanup service");
|
||||||
{
|
|
||||||
if (Activated)
|
|
||||||
await Perform();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay((int) TimeSpan.FromMinutes(WaitTime).TotalMilliseconds);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task TriggerPerform()
|
|
||||||
{
|
|
||||||
if (IsRunning)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
PerformTask = new Task(async () => await Perform());
|
|
||||||
PerformTask.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Perform()
|
Timer = new(TimeSpan.FromMinutes(config.GetValue<int>("Wait")));
|
||||||
{
|
|
||||||
if (IsRunning)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
Task.Run(Run);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Run()
|
||||||
|
{
|
||||||
|
while (await Timer.WaitForNextTickAsync())
|
||||||
|
{
|
||||||
IsRunning = true;
|
IsRunning = true;
|
||||||
StartedAt = DateTime.Now;
|
|
||||||
ServersRunning = 0;
|
|
||||||
|
|
||||||
OnUpdated?.Invoke(this, null);
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup");
|
||||||
|
|
||||||
using (var scope = ServiceScopeFactory.CreateScope())
|
var maxCpu = config.GetValue<int>("Cpu");
|
||||||
{
|
var minMemory = config.GetValue<int>("Memory");
|
||||||
// Setup time measure
|
var maxUptime = config.GetValue<int>("Uptime");
|
||||||
var watch = new Stopwatch();
|
var minUptime = config.GetValue<int>("MinUptime");
|
||||||
watch.Start();
|
|
||||||
|
|
||||||
// Get repos from dependency injection
|
|
||||||
var serverRepository = scope.ServiceProvider.GetRequiredService<ServerRepository>();
|
|
||||||
var nodeRepository = scope.ServiceProvider.GetRequiredService<NodeRepository>();
|
var nodeRepository = scope.ServiceProvider.GetRequiredService<NodeRepository>();
|
||||||
var nodeService = scope.ServiceProvider.GetRequiredService<NodeService>();
|
var nodeService = scope.ServiceProvider.GetRequiredService<NodeService>();
|
||||||
var imageRepo = scope.ServiceProvider.GetRequiredService<ImageRepository>();
|
|
||||||
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
|
||||||
|
|
||||||
// Fetching data from mysql
|
var nodes = nodeRepository
|
||||||
var servers = serverRepository.Get()
|
.Get()
|
||||||
.Include(x => x.Image)
|
|
||||||
.ToArray();
|
.ToArray();
|
||||||
var nodes = nodeRepository.Get().ToArray();
|
|
||||||
var exceptions = servers.Where(x => x.IsCleanupException);
|
|
||||||
var images = imageRepo.Get().ToArray();
|
|
||||||
|
|
||||||
var nodeCount = nodes.Count();
|
|
||||||
|
|
||||||
// We use this counter for the foreach loops
|
|
||||||
int counter = 0;
|
|
||||||
PercentProgress = 0;
|
|
||||||
|
|
||||||
// Fetching data from nodes so we know what nodes to scan
|
|
||||||
var nodeContainers = new Dictionary<Node, ContainerStats.Container[]>();
|
|
||||||
|
|
||||||
Status = "Checking Nodes";
|
|
||||||
counter = 0;
|
|
||||||
PercentProgress = 0;
|
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
|
|
||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cpu = await nodeService.GetCpuStats(node);
|
var cpuStats = await nodeService.GetCpuStats(node);
|
||||||
var freeMemory = await nodeService.GetMemoryStats(node);
|
var memoryStats = await nodeService.GetMemoryStats(node);
|
||||||
|
|
||||||
if (cpu.Usage > RequiredCpu || freeMemory.Free < RequiredMemory)
|
if (cpuStats.Usage > maxCpu || memoryStats.Free < minMemory)
|
||||||
{
|
{
|
||||||
var c = await nodeService.GetContainerStats(node);
|
var containerStats = await nodeService.GetContainerStats(node);
|
||||||
var containers = c.Containers;
|
|
||||||
nodeContainers.Add(node, containers.ToArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Error($"Error fetching cleanup data from node {node.Id}");
|
|
||||||
Logger.Error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
counter++;
|
var serverRepository = scope.ServiceProvider.GetRequiredService<ServerRepository>();
|
||||||
CalculateAndUpdateProgress(counter, nodeCount);
|
var imageRepository = scope.ServiceProvider.GetRequiredService<ImageRepository>();
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Searching for servers we can actually stop because they have the cleanup tag
|
var images = imageRepository
|
||||||
// and determine which servers we have to check for an illegal mc server
|
|
||||||
var serversToCheck = new List<Server>();
|
|
||||||
var serversToCheckForMc = new List<Server>();
|
|
||||||
|
|
||||||
Status = "Checking found servers";
|
|
||||||
counter = 0;
|
|
||||||
PercentProgress = 0;
|
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
|
|
||||||
// Count every container for progress calculation
|
|
||||||
var allContainers = 0;
|
|
||||||
foreach (var array in nodeContainers)
|
|
||||||
{
|
|
||||||
allContainers += array.Value.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var nodeContainer in nodeContainers)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var container in nodeContainer.Value)
|
|
||||||
{
|
|
||||||
var server = servers.First(x => x.Uuid.ToString() == container.Name);
|
|
||||||
var tagsJson = imageRepo
|
|
||||||
.Get()
|
.Get()
|
||||||
.First(x => x.Id == server.Image.Id).TagsJson;
|
.ToArray();
|
||||||
|
|
||||||
var tags = JsonConvert.DeserializeObject<string[]>(tagsJson) ?? Array.Empty<string>();
|
var imagesWithFlag = images
|
||||||
|
.Where(x =>
|
||||||
|
(JsonConvert.DeserializeObject<string[]>(x.TagsJson) ?? Array.Empty<string>()).Contains("cleanup")
|
||||||
|
)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
if (tags.FirstOrDefault(x => x == "cleanup") != null)
|
var containerMappedToServers = new Dictionary<ContainerStats.Container, Server>();
|
||||||
|
|
||||||
|
foreach (var container in containerStats.Containers)
|
||||||
{
|
{
|
||||||
serversToCheck.Add(server);
|
if (Guid.TryParse(container.Name, out Guid uuid))
|
||||||
}
|
|
||||||
|
|
||||||
if (tags.FirstOrDefault(x => x == "illegalmc") != null)
|
|
||||||
{
|
{
|
||||||
serversToCheckForMc.Add(server);
|
var server = serverRepository
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Error($"Error processing cleanup data from node {nodeContainer.Key.Id}");
|
|
||||||
Logger.Error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
counter++;
|
|
||||||
CalculateAndUpdateProgress(counter, allContainers);
|
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we gonna scan every tagged server
|
|
||||||
Status = "Scanning servers";
|
|
||||||
counter = 0;
|
|
||||||
PercentProgress = 0;
|
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
|
|
||||||
foreach (var server in serversToCheck)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var serverData = serverRepository
|
|
||||||
.Get()
|
.Get()
|
||||||
|
.Include(x => x.Image)
|
||||||
.Include(x => x.MainAllocation)
|
.Include(x => x.MainAllocation)
|
||||||
.Include(x => x.Node)
|
|
||||||
.Include(x => x.Variables)
|
.Include(x => x.Variables)
|
||||||
.First(x => x.Id == server.Id);
|
.FirstOrDefault(x => x.Uuid == uuid);
|
||||||
|
|
||||||
var players = GetPlayers(serverData.Node, serverData.MainAllocation);
|
if (server != null && imagesWithFlag.Any(y => y.Id == server.Image.Id))
|
||||||
|
{
|
||||||
|
containerMappedToServers.Add(container, server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
||||||
|
|
||||||
|
foreach (var containerMapped in containerMappedToServers)
|
||||||
|
{
|
||||||
|
var server = containerMapped.Value;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
var stats = await serverService.GetDetails(server);
|
var stats = await serverService.GetDetails(server);
|
||||||
|
|
||||||
var exception = exceptions.FirstOrDefault(x => x.Id == server.Id) != null;
|
if (server.IsCleanupException)
|
||||||
|
{
|
||||||
|
if (stats.Utilization.Uptime > TimeSpan.FromHours(maxUptime).TotalMilliseconds)
|
||||||
|
{
|
||||||
|
var players = GetPlayers(node, server.MainAllocation);
|
||||||
|
|
||||||
if (stats != null)
|
if (players == 0)
|
||||||
{
|
|
||||||
if (exception)
|
|
||||||
{
|
|
||||||
if (players == 0 && stats.Utilization.Uptime > TimeSpan.FromHours(6).TotalMilliseconds)
|
|
||||||
{
|
{
|
||||||
await serverService.SetPowerState(server, PowerSignal.Restart);
|
await serverService.SetPowerState(server, PowerSignal.Restart);
|
||||||
|
|
||||||
ServersCleaned++;
|
ServersCleaned++;
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ServersRunning++;
|
ServersRunning++;
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (players == 0 && stats.Utilization.Uptime > TimeSpan.FromMinutes(10).TotalMilliseconds)
|
|
||||||
{
|
|
||||||
var cleanupVar = serverData.Variables.FirstOrDefault(x => x.Key == "J2S");
|
|
||||||
|
|
||||||
if (cleanupVar == null)
|
await MessageService.Emit("cleanup.updated", null);
|
||||||
{
|
}
|
||||||
await serverService.SetPowerState(server, PowerSignal.Stop);
|
|
||||||
ServersCleaned++;
|
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (cleanupVar.Value == "1")
|
if (stats.Utilization.Uptime > TimeSpan.FromMinutes(minUptime).TotalMilliseconds)
|
||||||
|
{
|
||||||
|
var players = GetPlayers(node, server.MainAllocation);
|
||||||
|
|
||||||
|
if (players < 1)
|
||||||
|
{
|
||||||
|
var j2SVar = server.Variables.FirstOrDefault(x => x.Key == "J2S");
|
||||||
|
var handleJ2S = j2SVar != null && j2SVar.Value == "1";
|
||||||
|
|
||||||
|
if (handleJ2S)
|
||||||
{
|
{
|
||||||
await serverService.SetPowerState(server, PowerSignal.Restart);
|
await serverService.SetPowerState(server, PowerSignal.Restart);
|
||||||
ServersCleaned++;
|
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await serverService.SetPowerState(server, PowerSignal.Stop);
|
await serverService.SetPowerState(server, PowerSignal.Stop);
|
||||||
|
}
|
||||||
|
|
||||||
ServersCleaned++;
|
ServersCleaned++;
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ServersRunning++;
|
ServersRunning++;
|
||||||
OnUpdated?.Invoke(this, null);
|
}
|
||||||
|
|
||||||
|
await MessageService.Emit("cleanup.updated", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Warn($"Error checking server {server.Name} ({server.Id})");
|
||||||
|
Logger.Warn(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error($"Error scanning {server.Name}");
|
Logger.Error($"Error performing cleanup on node {node.Name} ({node.Id})");
|
||||||
Logger.Error(e);
|
Logger.Error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
counter++;
|
|
||||||
CalculateAndUpdateProgress(counter, serversToCheck.Count);
|
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally we have to check all code container allocations
|
|
||||||
// for illegal hosted mc servers
|
|
||||||
|
|
||||||
Status = "Scanning code containers";
|
|
||||||
counter = 0;
|
|
||||||
PercentProgress = 0;
|
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
|
|
||||||
foreach (var server in serversToCheckForMc)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var serverData = serverRepository
|
|
||||||
.Get()
|
|
||||||
.Include(x => x.Allocations)
|
|
||||||
.Include(x => x.Node)
|
|
||||||
.First(x => x.Id == server.Id);
|
|
||||||
|
|
||||||
foreach (var allocation in serverData.Allocations)
|
|
||||||
{
|
|
||||||
if (GetPlayers(server.Node, allocation) != -1)
|
|
||||||
{
|
|
||||||
// TODO: Suspend server
|
|
||||||
Logger.Warn("Found CC running mc: https://moonlight.endelon-hosting.de/server/" +
|
|
||||||
server.Uuid + "/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Error($"Error scanning (cc) {server.Name}");
|
|
||||||
Logger.Error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
counter++;
|
|
||||||
CalculateAndUpdateProgress(counter, serversToCheckForMc.Count);
|
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
watch.Stop();
|
|
||||||
|
|
||||||
Status = $"Cleanup finished. Duration: {Math.Round(TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).TotalMinutes, 2)} Minutes";
|
|
||||||
PercentProgress = 100;
|
|
||||||
OnUpdated?.Invoke(this, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
CompletedAt = DateTime.Now;
|
|
||||||
CleanupsPerformed++;
|
CleanupsPerformed++;
|
||||||
OnUpdated?.Invoke(this, null);
|
await MessageService.Emit("cleanup.updated", null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetPlayers(Node node, NodeAllocation allocation)
|
private int GetPlayers(Node node, NodeAllocation allocation)
|
||||||
{
|
{
|
||||||
var ms = new MineStat(node.Fqdn, (ushort)allocation.Port);
|
var ms = new MineStat(node.Fqdn, (ushort)allocation.Port);
|
||||||
|
|
||||||
|
//TODO: Add fake player check
|
||||||
|
|
||||||
if (ms.ServerUp)
|
if (ms.ServerUp)
|
||||||
{
|
{
|
||||||
return ms.CurrentPlayersInt;
|
return ms.CurrentPlayersInt;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void CalculateAndUpdateProgress(int now, int all)
|
|
||||||
{
|
|
||||||
PercentProgress = (int)Math.Round((now / (double)all) * 100);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Logging.Net;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
|
|
||||||
@@ -21,6 +22,9 @@ public class ConfigService : IConfiguration
|
|||||||
|
|
||||||
if (debugVar != null)
|
if (debugVar != null)
|
||||||
DebugMode = bool.Parse(debugVar);
|
DebugMode = bool.Parse(debugVar);
|
||||||
|
|
||||||
|
if(DebugMode)
|
||||||
|
Logger.Debug("Debug mode enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IConfigurationSection> GetChildren()
|
public IEnumerable<IConfigurationSection> GetChildren()
|
||||||
|
|||||||
@@ -9,6 +9,34 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject CookieService CookieService
|
@inject CookieService CookieService
|
||||||
|
|
||||||
|
<div class="menu menu-column justify-content-center"
|
||||||
|
data-kt-menu="true">
|
||||||
|
<div class="menu-item">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-success dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||||
|
<TL>Create</TL>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item py-2" href="/servers/create">
|
||||||
|
<TL>Server</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item py-2" href="/domains/create">
|
||||||
|
<TL>Domain</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item py-2" href="/websites/create">
|
||||||
|
<TL>Website</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="app-navbar flex-shrink-0">
|
<div class="app-navbar flex-shrink-0">
|
||||||
<div class="app-navbar-item ms-1 ms-lg-3">
|
<div class="app-navbar-item ms-1 ms-lg-3">
|
||||||
<ThemeSwitcher
|
<ThemeSwitcher
|
||||||
@@ -51,10 +79,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="separator my-2"></div>
|
<div class="separator my-2"></div>
|
||||||
<div class="menu-item px-5 my-1">
|
<div class="menu-item px-5 my-1">
|
||||||
<a href="/profile" class="menu-link px-5"><TL>Profile</TL></a>
|
<a href="/profile" class="menu-link px-5">
|
||||||
|
<TL>Profile</TL>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item px-5">
|
<div class="menu-item px-5">
|
||||||
<a @onclick="Logout" class="menu-link px-5"><TL>Logout</TL></a>
|
<a @onclick="Logout" class="menu-link px-5">
|
||||||
|
<TL>Logout</TL>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -53,6 +53,8 @@
|
|||||||
if(Tags.Contains("pythonfile"))
|
if(Tags.Contains("pythonfile"))
|
||||||
Settings.Add("Python file", typeof(PythonFileSetting));
|
Settings.Add("Python file", typeof(PythonFileSetting));
|
||||||
|
|
||||||
|
Settings.Add("Server reset", typeof(ServerResetSetting));
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
@using Moonlight.App.Helpers.Files
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject AlertService AlertService
|
||||||
|
@inject ToastService ToastService
|
||||||
|
@inject WingsApiHelper WingsApiHelper
|
||||||
|
@inject ConfigService ConfigService
|
||||||
|
@inject ServerService ServerService
|
||||||
|
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Reset"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Resetting"))"
|
||||||
|
CssClasses="btn-danger"
|
||||||
|
OnClick="OnClick">
|
||||||
|
</WButton>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[CascadingParameter]
|
||||||
|
public Server CurrentServer { get; set; }
|
||||||
|
|
||||||
|
private async Task OnClick()
|
||||||
|
{
|
||||||
|
var b = await AlertService.YesNo(
|
||||||
|
SmartTranslateService.Translate("Are you sure you want to reset this server?"),
|
||||||
|
SmartTranslateService.Translate("Are you sure? This cannot be undone"),
|
||||||
|
SmartTranslateService.Translate("Yes"),
|
||||||
|
SmartTranslateService.Translate("No")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (b)
|
||||||
|
{
|
||||||
|
await ToastService.CreateProcessToast("serverReset", SmartTranslateService.Translate("Resetting server"));
|
||||||
|
|
||||||
|
var access = new WingsFileAccess(
|
||||||
|
WingsApiHelper,
|
||||||
|
null!,
|
||||||
|
CurrentServer,
|
||||||
|
ConfigService,
|
||||||
|
null!
|
||||||
|
);
|
||||||
|
|
||||||
|
var files = await access.Ls();
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
await access.Delete(file);
|
||||||
|
await ToastService.UpdateProcessToast("serverReset", $"{SmartTranslateService.Translate("Deleted file")} {i}/{files.Length}");
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ToastService.UpdateProcessToast("serverReset", SmartTranslateService.Translate("Reinstalling server"));
|
||||||
|
|
||||||
|
await ServerService.Reinstall(CurrentServer);
|
||||||
|
await ToastService.RemoveProcessToast("serverReset");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
Moonlight/Shared/Views/Admin/Servers/Cleanup.razor
Normal file
103
Moonlight/Shared/Views/Admin/Servers/Cleanup.razor
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
@page "/admin/servers/cleanup"
|
||||||
|
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Models.Misc
|
||||||
|
@using Moonlight.App.Services.LogServices
|
||||||
|
|
||||||
|
@inject CleanupService CleanupService
|
||||||
|
@inject AuditLogService AuditLogService
|
||||||
|
@inject MessageService MessageService
|
||||||
|
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<div class="row g-5 g-xl-10 mb-5 mb-xl-10">
|
||||||
|
<div class="col-xl-3">
|
||||||
|
<div class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-end h-xl-100" style="background-color: #170049;">
|
||||||
|
<div class="card-header pt-5 mb-3">
|
||||||
|
<div class="d-flex flex-center rounded-circle h-80px w-80px" style="border: 1px rgba(255, 255, 255, 0.4);background-color: #7239EA">
|
||||||
|
<i class="text-white bx bxs-skull bx-lg"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body d-flex align-items-end mb-3">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<span class="fs-4hx text-white fw-bold me-6">@(CleanupService.ServersCleaned)</span>
|
||||||
|
<div class="fw-bold fs-6 text-white">
|
||||||
|
<span class="d-block">
|
||||||
|
<TL>Servers</TL>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<TL>stopped</TL>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-3">
|
||||||
|
<div class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-end h-xl-100" style="background-color: #170049;">
|
||||||
|
<div class="card-header pt-5 mb-3">
|
||||||
|
<div class="d-flex flex-center rounded-circle h-80px w-80px" style="border: 1px rgba(255, 255, 255, 0.4);background-color: #7239EA">
|
||||||
|
<i class="text-white bx bx-transfer bx-lg"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body d-flex align-items-end mb-3">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<span class="fs-4hx text-white fw-bold me-6">@(CleanupService.CleanupsPerformed)</span>
|
||||||
|
<div class="fw-bold fs-6 text-white">
|
||||||
|
<span class="d-block">
|
||||||
|
<TL>Cleanups</TL>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<TL>executed</TL>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-3">
|
||||||
|
<div class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-end h-xl-100" style="background-color: #170049;">
|
||||||
|
<div class="card-header pt-5 mb-3">
|
||||||
|
<div class="d-flex flex-center rounded-circle h-80px w-80px" style="border: 1px rgba(255, 255, 255, 0.4);background-color: #7239EA">
|
||||||
|
<i class="text-white bx bx-rocket bx-lg"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body d-flex align-items-end mb-3">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<span class="fs-4hx text-white fw-bold me-6">@(CleanupService.ServersRunning)</span>
|
||||||
|
<div class="fw-bold fs-6 text-white">
|
||||||
|
<span class="d-block">
|
||||||
|
<TL>Running cleanup</TL>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<TL>servers</TL>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
MessageService.Subscribe<Cleanup, Object>("cleanup.updated", this, _ =>
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
MessageService.Unsubscribe("cleanup.updated", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
@page "/admin/servers/cleanup"
|
|
||||||
|
|
||||||
@using Moonlight.App.Services
|
|
||||||
@using Logging.Net
|
|
||||||
@using Moonlight.App.Models.Misc
|
|
||||||
@using Moonlight.App.Services.LogServices
|
|
||||||
|
|
||||||
@inject CleanupService CleanupService
|
|
||||||
@inject AuditLogService AuditLogService
|
|
||||||
|
|
||||||
@implements IDisposable
|
|
||||||
|
|
||||||
<PageTitle>
|
|
||||||
<TL>Cleanup</TL>
|
|
||||||
</PageTitle>
|
|
||||||
|
|
||||||
<IsAdmin>
|
|
||||||
<div class="row g-5 g-xl-10 mb-5 mb-xl-10">
|
|
||||||
<div class="col-xl-3">
|
|
||||||
<div class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-end h-xl-100" style="background-color: #170049;">
|
|
||||||
<div class="card-header pt-5 mb-3">
|
|
||||||
<div class="d-flex flex-center rounded-circle h-80px w-80px" style="border: 1px rgba(255, 255, 255, 0.4);background-color: #7239EA">
|
|
||||||
<i class="text-white bx bxs-skull bx-lg"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body d-flex align-items-end mb-3">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="fs-4hx text-white fw-bold me-6">@(CleanupService.ServersCleaned)</span>
|
|
||||||
<div class="fw-bold fs-6 text-white">
|
|
||||||
<span class="d-block">
|
|
||||||
<TL>Servers</TL>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<TL>stopped</TL>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xl-3">
|
|
||||||
<div class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-end h-xl-100" style="background-color: #170049;">
|
|
||||||
<div class="card-header pt-5 mb-3">
|
|
||||||
<div class="d-flex flex-center rounded-circle h-80px w-80px" style="border: 1px rgba(255, 255, 255, 0.4);background-color: #7239EA">
|
|
||||||
<i class="text-white bx bx-transfer bx-lg"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body d-flex align-items-end mb-3">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="fs-4hx text-white fw-bold me-6">@(CleanupService.CleanupsPerformed)</span>
|
|
||||||
<div class="fw-bold fs-6 text-white">
|
|
||||||
<span class="d-block">
|
|
||||||
<TL>Cleanups</TL>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<TL>executed</TL>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xl-3">
|
|
||||||
<div class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-end h-xl-100" style="background-color: #170049;">
|
|
||||||
<div class="card-header pt-5 mb-3">
|
|
||||||
<div class="d-flex flex-center rounded-circle h-80px w-80px" style="border: 1px rgba(255, 255, 255, 0.4);background-color: #7239EA">
|
|
||||||
<i class="text-white bx bx-rocket bx-lg"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body d-flex align-items-end mb-3">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="fs-4hx text-white fw-bold me-6">@(CleanupService.ServersRunning)</span>
|
|
||||||
<div class="fw-bold fs-6 text-white">
|
|
||||||
<span class="d-block">
|
|
||||||
<TL>Used clanup</TL>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<TL>Servers</TL>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xl-3">
|
|
||||||
<div class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-end h-xl-100" style="background-color: #170049;">
|
|
||||||
<div class="card-body d-flex align-items-end mb-3">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><TL>Status</TL>: @(CleanupService.Status)</label>
|
|
||||||
<div class="progress">
|
|
||||||
<div class="progress-bar progress-bar-striped progress-bar-animated bg-primary" role="progressbar" aria-valuenow="@(CleanupService.PercentProgress)" aria-valuemin="0" aria-valuemax="100" style="width: @(CleanupService.PercentProgress)%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xl-3">
|
|
||||||
<div class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-end">
|
|
||||||
<div class="card-body mb-3">
|
|
||||||
<div class="align-items-top">
|
|
||||||
<div class="fw-bold fs-6 text-white">
|
|
||||||
<div class="row mb-3">
|
|
||||||
@if (CleanupService.IsRunning)
|
|
||||||
{
|
|
||||||
<button class="btn btn-primary disabled" disabled="">
|
|
||||||
<TL>Start</TL>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<button @onclick="Trigger" class="btn btn-primary">
|
|
||||||
<TL>Start</TL>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="row mb-3">
|
|
||||||
@if (CleanupService.Activated)
|
|
||||||
{
|
|
||||||
<button class="btn btn-success disabled" disabled="">
|
|
||||||
<TL>Enable</TL>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<button @onclick="Enable" class="btn btn-success">
|
|
||||||
<TL>Enable</TL>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
@if (CleanupService.Activated)
|
|
||||||
{
|
|
||||||
<button @onclick="Disabled" class="btn btn-danger">
|
|
||||||
<TL>Disable</TL>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<button class="btn btn-danger disabled" disabled="">
|
|
||||||
<TL>Disable</TL>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</IsAdmin>
|
|
||||||
<NonAdmin>
|
|
||||||
<UnauthorizedAlert></UnauthorizedAlert>
|
|
||||||
</NonAdmin>
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
|
|
||||||
protected async override Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
CleanupService.OnUpdated += OnUpdated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUpdated(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
Logger.Debug(CleanupService.PercentProgress);
|
|
||||||
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
CleanupService.OnUpdated -= OnUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Enable()
|
|
||||||
{
|
|
||||||
CleanupService.Activated = true;
|
|
||||||
AuditLogService.Log(AuditLogType.CleanupEnabled, "The automatic cleanup has been activated");
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Disabled()
|
|
||||||
{
|
|
||||||
CleanupService.Activated = false;
|
|
||||||
AuditLogService.Log(AuditLogType.CleanupDisabled, "The automatic cleanup has been disabled");
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void Trigger()
|
|
||||||
{
|
|
||||||
await CleanupService.TriggerPerform();
|
|
||||||
await AuditLogService.Log(AuditLogType.CleanupTriggered, "The automatic cleanup has been manually triggered");
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -407,24 +407,34 @@ The State field is required.;The State field is required.
|
|||||||
The Country field is required.;The Country field is required.
|
The Country field is required.;The Country field is required.
|
||||||
Street and house number requered;Street and house number requered
|
Street and house number requered;Street and house number requered
|
||||||
Max lenght reached;Max lenght reached
|
Max lenght reached;Max lenght reached
|
||||||
Security;Security
|
Server;Server
|
||||||
Subscriptions;Subscriptions
|
stopped;stopped
|
||||||
Secure your account;Secure your account
|
Cleanups;Cleanups
|
||||||
2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.;2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.
|
executed;executed
|
||||||
enable;enable
|
Used clanup;Used clanup
|
||||||
Activate 2fa;Activate 2fa
|
|
||||||
2fa apps;2fa apps
|
|
||||||
Use an app like ;Use an app like
|
|
||||||
or;or
|
|
||||||
and scan the following QR Code;and scan the following QR Code
|
|
||||||
If you have trouble using the QR Code, select manual input in the app and enter your email and the following code:;If you have trouble using the QR Code, select manual input in the app and enter your email and the following code:
|
|
||||||
Finish activation;Finish activation
|
|
||||||
New password;New password
|
|
||||||
Enable;Enable
|
Enable;Enable
|
||||||
Your account is secured with 2fa;Your account is secured with 2fa
|
Disabble;Disabble
|
||||||
anyone write a fancy text here?;anyone write a fancy text here?
|
|
||||||
Disable;Disable
|
Disable;Disable
|
||||||
You need to enter a 2fa code;You need to enter a 2fa code
|
Addons;Addons
|
||||||
2FA code invalid;2FA code invalid
|
Javascript version;Javascript version
|
||||||
2fa Code;2fa Code
|
Javascript file;Javascript file
|
||||||
2fa Code requiered;2fa Code requiered
|
Select javascript file to execute on start;Select javascript file to execute on start
|
||||||
|
Submit;Submit
|
||||||
|
Processing;Processing
|
||||||
|
Go up;Go up
|
||||||
|
Running cleanup;Running cleanup
|
||||||
|
servers;servers
|
||||||
|
Select folder to move the file(s) to;Select folder to move the file(s) to
|
||||||
|
Paper version;Paper version
|
||||||
|
Join2Start;Join2Start
|
||||||
|
Server reset;Server reset
|
||||||
|
Reset;Reset
|
||||||
|
Resetting;Resetting
|
||||||
|
Are you sure you want to reset this server?;Are you sure you want to reset this server?
|
||||||
|
Are you sure? This cannot be undone;Are you sure? This cannot be undone
|
||||||
|
Resetting server;Resetting server
|
||||||
|
Deleted file;Deleted file
|
||||||
|
Reinstalling server;Reinstalling server
|
||||||
|
Uploading files;Uploading files
|
||||||
|
complete;complete
|
||||||
|
Upload complete;Upload complete
|
||||||
Reference in New Issue
Block a user