Started implementing schedules feature
This commit is contained in:
parent
d1f73e6d78
commit
a0ca9af5c9
13 changed files with 1382 additions and 1 deletions
|
@ -2,6 +2,7 @@
|
|||
using MoonCore.Helpers;
|
||||
using Moonlight.Features.Advertisement.Configuration;
|
||||
using Moonlight.Features.FileManager.Configuration;
|
||||
using Moonlight.Features.Servers.Configuration;
|
||||
using Moonlight.Features.StoreSystem.Configuration;
|
||||
using Moonlight.Features.Theming.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -27,6 +28,8 @@ public class ConfigV1
|
|||
|
||||
[JsonProperty("WebServer")] public WebServerData WebServer { get; set; } = new();
|
||||
|
||||
[JsonProperty("Servers")] public ServersData Servers { get; set; } = new();
|
||||
|
||||
public class WebServerData
|
||||
{
|
||||
[JsonProperty("HttpUploadLimit")]
|
||||
|
|
|
@ -51,6 +51,7 @@ public class DataContext : DbContext
|
|||
public DbSet<ServerVariable> ServerVariables { get; set; }
|
||||
public DbSet<ServerDockerImage> ServerDockerImages { get; set; }
|
||||
public DbSet<ServerImageVariable> ServerImageVariables { get; set; }
|
||||
public DbSet<ServerSchedule> ServerSchedules { get; set; }
|
||||
|
||||
public DataContext(ConfigService<ConfigV1> configService)
|
||||
{
|
||||
|
|
1089
Moonlight/Core/Database/Migrations/20240214091019_AddedServerSchedules.Designer.cs
generated
Normal file
1089
Moonlight/Core/Database/Migrations/20240214091019_AddedServerSchedules.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.Core.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedServerSchedules : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ServerSchedules",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Cron = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ActionType = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ActionData = table.Column<string>(type: "TEXT", nullable: false),
|
||||
LastRunAt = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
WasLastRunAutomatic = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ServerId = table.Column<int>(type: "INTEGER", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ServerSchedules", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ServerSchedules_Servers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalTable: "Servers",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ServerSchedules_ServerId",
|
||||
table: "ServerSchedules",
|
||||
column: "ServerId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ServerSchedules");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -398,6 +398,43 @@ namespace Moonlight.Core.Database.Migrations
|
|||
b.ToTable("ServerNodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerSchedule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ActionData")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ActionType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Cron")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastRunAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ServerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("WasLastRunAutomatic")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("ServerSchedules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerVariable", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -871,6 +908,13 @@ namespace Moonlight.Core.Database.Migrations
|
|||
.HasForeignKey("ServerImageId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerSchedule", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.Features.Servers.Entities.Server", null)
|
||||
.WithMany("Schedules")
|
||||
.HasForeignKey("ServerId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.Features.Servers.Entities.ServerVariable", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.Features.Servers.Entities.Server", null)
|
||||
|
@ -1010,6 +1054,8 @@ namespace Moonlight.Core.Database.Migrations
|
|||
{
|
||||
b.Navigation("Allocations");
|
||||
|
||||
b.Navigation("Schedules");
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ public class ServerServiceDefinition : ServiceDefinition
|
|||
await context.AddPage<Console>("Console", "/", "bx bx-sm bxs-terminal");
|
||||
await context.AddPage<Files>("Files", "/files", "bx bx-sm bxs-folder");
|
||||
await context.AddPage<Network>("Network", "/network", "bx bx-sm bx-cloud");
|
||||
await context.AddPage<Schedules>("Schedules", "/schedules", "bx bx-sm bx-timer");
|
||||
await context.AddPage<Variables>("Variables", "/variables", "bx bx-sm bx-slider");
|
||||
await context.AddPage<Reset>("Reset", "/reset", "bx bx-sm bx-revision");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
using Cronos;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Abstractions;
|
||||
using MoonCore.Helpers;
|
||||
using MoonCore.Services;
|
||||
using Moonlight.Core.Configuration;
|
||||
using Moonlight.Features.Servers.Entities;
|
||||
using Moonlight.Features.Servers.Entities.Enums;
|
||||
using Moonlight.Features.Servers.Services;
|
||||
using BackgroundService = MoonCore.Abstractions.BackgroundService;
|
||||
|
||||
namespace Moonlight.Features.Servers.BackgroundServices;
|
||||
|
||||
public class ScheduleRunnerService : BackgroundService
|
||||
{
|
||||
private readonly IServiceProvider ServiceProvider;
|
||||
private readonly ConfigService<ConfigV1> ConfigService;
|
||||
|
||||
public ScheduleRunnerService(IServiceProvider serviceProvider, ConfigService<ConfigV1> configService)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
ConfigService = configService;
|
||||
}
|
||||
|
||||
public override async Task Run()
|
||||
{
|
||||
var config = ConfigService.Get().Servers.Schedules;
|
||||
|
||||
if(config.Disable)
|
||||
Logger.Info("Server schedules are disabled");
|
||||
|
||||
while (!Cancellation.IsCancellationRequested)
|
||||
{
|
||||
Logger.Debug("Performing scheduler run");
|
||||
|
||||
// Resolve services from di
|
||||
using var scope = ServiceProvider.CreateScope();
|
||||
var serverRepo = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
|
||||
var scheduleRepo = scope.ServiceProvider.GetRequiredService<Repository<ServerSchedule>>();
|
||||
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
||||
|
||||
// Load required data
|
||||
var servers = serverRepo
|
||||
.Get()
|
||||
.Include(x => x.Schedules)
|
||||
.Where(x => x.Schedules.Any())
|
||||
.ToArray();
|
||||
|
||||
Logger.Debug($"Found {servers.Length} servers with schedules");
|
||||
|
||||
foreach (var server in servers)
|
||||
{
|
||||
foreach (var schedule in server.Schedules)
|
||||
{
|
||||
if (!CronExpression.TryParse(schedule.Cron, CronFormat.Standard, out CronExpression cronExpression))
|
||||
{
|
||||
Logger.Warn($"Unable to parse cron expression for schedule '{schedule.Name}' for server '{server.Id}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
var nextRun = cronExpression.GetNextOccurrence(schedule.LastRunAt, TimeZoneInfo.Utc);
|
||||
|
||||
if (nextRun == null)
|
||||
{
|
||||
Logger.Warn($"Unable to determine next run time for schedule '{schedule.Name}' for server '{server.Id}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the next run is in the past
|
||||
if (DateTime.UtcNow > nextRun)
|
||||
{
|
||||
var diff = DateTime.UtcNow - nextRun;
|
||||
|
||||
if(diff.Value.TotalMinutes > 0)
|
||||
Logger.Warn($"Missed executing schedule '{schedule.Name}' for server '{server.Id}'. Was moonlight offline for a while?");
|
||||
else
|
||||
Logger.Warn($"Missed executing schedule '{schedule.Name}' for server '{server.Id}'. The miss difference ({diff.Value.TotalSeconds}s) indicate a miss configuration. Increase your time drift or lower your check delay to fix the error");
|
||||
|
||||
schedule.LastRunAt = DateTime.UtcNow;
|
||||
schedule.WasLastRunAutomatic = false;
|
||||
|
||||
scheduleRepo.Update(schedule);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the next run is too far in the future
|
||||
if ((nextRun - DateTime.UtcNow).Value.TotalSeconds > config.TimeDrift)
|
||||
continue;
|
||||
|
||||
// Its time to execute the schedule :D
|
||||
await RunSchedule(server, schedule, scope.ServiceProvider);
|
||||
|
||||
schedule.LastRunAt = DateTime.UtcNow;
|
||||
schedule.WasLastRunAutomatic = true;
|
||||
|
||||
scheduleRepo.Update(schedule);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(config.CheckDelay, Cancellation.Token);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RunSchedule(Server server, ServerSchedule schedule, IServiceProvider? provider = null)
|
||||
{
|
||||
IServiceProvider serviceProvider;
|
||||
|
||||
if (provider != null)
|
||||
serviceProvider = provider;
|
||||
else
|
||||
serviceProvider = ServiceProvider.CreateScope().ServiceProvider;
|
||||
|
||||
switch (schedule.ActionType)
|
||||
{
|
||||
case ScheduleActionType.Power:
|
||||
|
||||
//TODO: Implement actual action here
|
||||
|
||||
break;
|
||||
default:
|
||||
Logger.Warn($"Schedule action type {schedule.ActionType} has not been implemented yet");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
30
Moonlight/Features/Servers/Configuration/ServersData.cs
Normal file
30
Moonlight/Features/Servers/Configuration/ServersData.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.Features.Servers.Configuration;
|
||||
|
||||
public class ServersData
|
||||
{
|
||||
[JsonProperty("Schedules")] public SchedulesData Schedules { get; set; } = new();
|
||||
|
||||
public class SchedulesData
|
||||
{
|
||||
[JsonProperty("Disable")]
|
||||
[Description("This flag stops the schedule execution system from starting. Changing this requires a restart")]
|
||||
public bool Disable { get; set; } = false;
|
||||
|
||||
[JsonProperty("SchedulePerServer")]
|
||||
[Description("This specifies the schedules a user can create for a server")]
|
||||
public int SchedulePerServer { get; set; } = 10;
|
||||
|
||||
[JsonProperty("CheckDelay")]
|
||||
[Description("The delay in seconds between every schedule run")]
|
||||
public int CheckDelay { get; set; } = 10;
|
||||
|
||||
[JsonProperty("TimeDrift")]
|
||||
[Description(
|
||||
"The amount of seconds the planned execution time is allowed to vary to the current time and still be executed")]
|
||||
public int TimeDrift { get; set; } = 30;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace Moonlight.Features.Servers.Entities.Enums;
|
||||
|
||||
public enum ScheduleActionType
|
||||
{
|
||||
Command = 0,
|
||||
Power = 1,
|
||||
Backup = 2
|
||||
}
|
|
@ -19,4 +19,6 @@ public class Server
|
|||
public ServerNode Node { get; set; }
|
||||
public ServerAllocation MainAllocation { get; set; }
|
||||
public List<ServerAllocation> Allocations { get; set; } = new();
|
||||
|
||||
public List<ServerSchedule> Schedules { get; set; } = new();
|
||||
}
|
16
Moonlight/Features/Servers/Entities/ServerSchedule.cs
Normal file
16
Moonlight/Features/Servers/Entities/ServerSchedule.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using Moonlight.Features.Servers.Entities.Enums;
|
||||
|
||||
namespace Moonlight.Features.Servers.Entities;
|
||||
|
||||
public class ServerSchedule
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Cron { get; set; }
|
||||
|
||||
public ScheduleActionType ActionType { get; set; }
|
||||
public string ActionData { get; set; } = "";
|
||||
|
||||
public DateTime LastRunAt { get; set; } = DateTime.Now;
|
||||
public bool WasLastRunAutomatic { get; set; } = false;
|
||||
}
|
9
Moonlight/Features/Servers/UI/UserViews/Schedules.razor
Normal file
9
Moonlight/Features/Servers/UI/UserViews/Schedules.razor
Normal file
|
@ -0,0 +1,9 @@
|
|||
@using Moonlight.Features.Servers.Entities
|
||||
|
||||
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public Server Server { get; set; }
|
||||
}
|
|
@ -43,7 +43,6 @@
|
|||
<Folder Include="Features\FileManager\Models\Forms\" />
|
||||
<Folder Include="Features\FileManager\UI\Views\" />
|
||||
<Folder Include="Features\Servers\Api\Resources\" />
|
||||
<Folder Include="Features\Servers\Configuration\" />
|
||||
<Folder Include="Features\Servers\Http\Resources\" />
|
||||
<Folder Include="Features\StoreSystem\Helpers\" />
|
||||
<Folder Include="Features\Ticketing\Models\Abstractions\" />
|
||||
|
@ -52,6 +51,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||
<PackageReference Include="BlazorTable" Version="1.17.0" />
|
||||
<PackageReference Include="Cronos" Version="0.8.3" />
|
||||
<PackageReference Include="FluentFTP" Version="49.0.2" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="8.0.746" />
|
||||
<PackageReference Include="JWT" Version="10.1.1" />
|
||||
|
|
Loading…
Reference in a new issue