Compare commits
29 Commits
main
...
v2.1_Refac
| Author | SHA1 | Date | |
|---|---|---|---|
| f6b71f4de6 | |||
| 85392208c4 | |||
| 91fb15a03e | |||
| 32f447d268 | |||
| 160446eed0 | |||
| b90100d250 | |||
| 282096595d | |||
| 348e9560ab | |||
| 7587a7e8e3 | |||
| 7c2bc9d19b | |||
| 2e4c933fbe | |||
| 5c170935b4 | |||
| eaf8c36f7f | |||
| bb81ca9674 | |||
| f57d33cb1e | |||
| b546a168d2 | |||
| 84b3d1caf6 | |||
| 0bef60dbc8 | |||
| bdc4ad8265 | |||
| 431cdcb260 | |||
| 1f94752c54 | |||
| a2db7be26f | |||
| 265a4b280b | |||
| f8c11b2dd8 | |||
| e83d1351cb | |||
| 61253919cf | |||
| 383d4bb24b | |||
| f22f0c0e51 | |||
| 514f862a9d |
10
MoonlightServers.ApiServer.Runtime/DevPluginLoader.cs
Normal file
10
MoonlightServers.ApiServer.Runtime/DevPluginLoader.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using MoonCore.PluginFramework;
|
||||||
|
using Moonlight.ApiServer.Plugins;
|
||||||
|
|
||||||
|
namespace MoonlightServers.ApiServer.Runtime;
|
||||||
|
|
||||||
|
[PluginLoader]
|
||||||
|
public partial class DevPluginLoader : IPluginStartup
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<NoDefaultLaunchSettingsFile>True</NoDefaultLaunchSettingsFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MoonlightServers.ApiServer\MoonlightServers.ApiServer.csproj"/>
|
||||||
|
<ProjectReference Include="..\MoonlightServers.Frontend.Runtime\MoonlightServers.Frontend.Runtime.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.9" />
|
||||||
|
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.9"/>
|
||||||
|
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.3"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Update="Properties\launchSettings.json">
|
||||||
|
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="wwwroot\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
34
MoonlightServers.ApiServer.Runtime/Program.cs
Normal file
34
MoonlightServers.ApiServer.Runtime/Program.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Startup;
|
||||||
|
using MoonlightServers.ApiServer.Runtime;
|
||||||
|
|
||||||
|
var pluginLoader = new DevPluginLoader();
|
||||||
|
pluginLoader.Initialize();
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.AddMoonlight(pluginLoader.Instances);
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.UseMoonlight(pluginLoader.Instances);
|
||||||
|
|
||||||
|
// Add frontend
|
||||||
|
var configuration = AppConfiguration.CreateEmpty();
|
||||||
|
builder.Configuration.Bind(configuration);
|
||||||
|
|
||||||
|
// Handle setup of wasm app hosting in the runtime
|
||||||
|
// so the Moonlight.ApiServer doesn't need the wasm package
|
||||||
|
if (configuration.Frontend.EnableHosting)
|
||||||
|
{
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
app.UseWebAssemblyDebugging();
|
||||||
|
|
||||||
|
app.UseBlazorFrameworkFiles();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.MapMoonlight(pluginLoader.Instances);
|
||||||
|
|
||||||
|
|
||||||
|
await app.RunAsync();
|
||||||
@@ -18,8 +18,4 @@ public class Node
|
|||||||
public int HttpPort { get; set; }
|
public int HttpPort { get; set; }
|
||||||
public int FtpPort { get; set; }
|
public int FtpPort { get; set; }
|
||||||
public bool UseSsl { get; set; }
|
public bool UseSsl { get; set; }
|
||||||
|
|
||||||
// Misc
|
|
||||||
public bool EnableTransparentMode { get; set; }
|
|
||||||
public bool EnableDynamicFirewall { get; set; }
|
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,4 @@ public class Server
|
|||||||
public int Cpu { get; set; }
|
public int Cpu { get; set; }
|
||||||
public int Memory { get; set; }
|
public int Memory { get; set; }
|
||||||
public int Disk { get; set; }
|
public int Disk { get; set; }
|
||||||
public bool UseVirtualDisk { get; set; }
|
|
||||||
public int Bandwidth { get; set; }
|
|
||||||
}
|
}
|
||||||
@@ -1,452 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using MoonlightServers.ApiServer.Database;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Database.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(ServersDataContext))]
|
|
||||||
[Migration("20250226210232_RecreatedMigrationsForPostgresql")]
|
|
||||||
partial class RecreatedMigrationsForPostgresql
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "8.0.11")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<string>("IpAddress")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("NodeId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("Port")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int?>("ServerId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("NodeId");
|
|
||||||
|
|
||||||
b.HasIndex("ServerId");
|
|
||||||
|
|
||||||
b.ToTable("Servers_Allocations", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<bool>("EnableDynamicFirewall")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<bool>("EnableTransparentMode")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<string>("Fqdn")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("FtpPort")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("HttpPort")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Token")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<bool>("UseSsl")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Servers_Nodes", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<int>("Bandwidth")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("Cpu")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("Disk")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("DockerImageIndex")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("Memory")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("NodeId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("OwnerId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("StarId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("StartupOverride")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<bool>("UseVirtualDisk")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("NodeId");
|
|
||||||
|
|
||||||
b.HasIndex("StarId");
|
|
||||||
|
|
||||||
b.ToTable("Servers_Servers", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<bool>("Completed")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CompletedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<int?>("ServerId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<long>("Size")
|
|
||||||
.HasColumnType("bigint");
|
|
||||||
|
|
||||||
b.Property<bool>("Successful")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("ServerId");
|
|
||||||
|
|
||||||
b.ToTable("Servers_ServerBackups", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<string>("Key")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("ServerId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("Value")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("ServerId");
|
|
||||||
|
|
||||||
b.ToTable("Servers_ServerVariables", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<bool>("AllowDockerImageChange")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<string>("Author")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("DefaultDockerImage")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("DonateUrl")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("InstallDockerImage")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("InstallScript")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("InstallShell")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("OnlineDetection")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("ParseConfiguration")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("RequiredAllocations")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("StartupCommand")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("StopCommand")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("UpdateUrl")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Version")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Servers_Stars", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<bool>("AutoPulling")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<string>("DisplayName")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Identifier")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("StarId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("StarId");
|
|
||||||
|
|
||||||
b.ToTable("Servers_StarDockerImages", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<bool>("AllowEditing")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<bool>("AllowViewing")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<string>("DefaultValue")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Filter")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Key")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("StarId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("Type")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("StarId");
|
|
||||||
|
|
||||||
b.ToTable("Servers_StarVariables", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
|
|
||||||
.WithMany("Allocations")
|
|
||||||
.HasForeignKey("NodeId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
|
|
||||||
.WithMany("Allocations")
|
|
||||||
.HasForeignKey("ServerId");
|
|
||||||
|
|
||||||
b.Navigation("Node");
|
|
||||||
|
|
||||||
b.Navigation("Server");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
|
|
||||||
.WithMany("Servers")
|
|
||||||
.HasForeignKey("NodeId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("StarId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Node");
|
|
||||||
|
|
||||||
b.Navigation("Star");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", null)
|
|
||||||
.WithMany("Backups")
|
|
||||||
.HasForeignKey("ServerId");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
|
|
||||||
.WithMany("Variables")
|
|
||||||
.HasForeignKey("ServerId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Server");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
|
|
||||||
.WithMany("DockerImages")
|
|
||||||
.HasForeignKey("StarId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Star");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
|
|
||||||
.WithMany("Variables")
|
|
||||||
.HasForeignKey("StarId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Star");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Allocations");
|
|
||||||
|
|
||||||
b.Navigation("Servers");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Allocations");
|
|
||||||
|
|
||||||
b.Navigation("Backups");
|
|
||||||
|
|
||||||
b.Navigation("Variables");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("DockerImages");
|
|
||||||
|
|
||||||
b.Navigation("Variables");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,456 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using MoonlightServers.ApiServer.Database;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Database.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(ServersDataContext))]
|
|
||||||
[Migration("20250301142415_AddedTokenIdField")]
|
|
||||||
partial class AddedTokenIdField
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "8.0.11")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<string>("IpAddress")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("NodeId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("Port")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int?>("ServerId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("NodeId");
|
|
||||||
|
|
||||||
b.HasIndex("ServerId");
|
|
||||||
|
|
||||||
b.ToTable("Servers_Allocations", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<bool>("EnableDynamicFirewall")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<bool>("EnableTransparentMode")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<string>("Fqdn")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("FtpPort")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("HttpPort")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Token")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("TokenId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<bool>("UseSsl")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Servers_Nodes", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<int>("Bandwidth")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("Cpu")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("Disk")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("DockerImageIndex")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("Memory")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("NodeId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("OwnerId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("StarId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("StartupOverride")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<bool>("UseVirtualDisk")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("NodeId");
|
|
||||||
|
|
||||||
b.HasIndex("StarId");
|
|
||||||
|
|
||||||
b.ToTable("Servers_Servers", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<bool>("Completed")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CompletedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<int?>("ServerId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<long>("Size")
|
|
||||||
.HasColumnType("bigint");
|
|
||||||
|
|
||||||
b.Property<bool>("Successful")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("ServerId");
|
|
||||||
|
|
||||||
b.ToTable("Servers_ServerBackups", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<string>("Key")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("ServerId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("Value")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("ServerId");
|
|
||||||
|
|
||||||
b.ToTable("Servers_ServerVariables", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<bool>("AllowDockerImageChange")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<string>("Author")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("DefaultDockerImage")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("DonateUrl")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("InstallDockerImage")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("InstallScript")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("InstallShell")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("OnlineDetection")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("ParseConfiguration")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("RequiredAllocations")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("StartupCommand")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("StopCommand")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("UpdateUrl")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Version")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Servers_Stars", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<bool>("AutoPulling")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<string>("DisplayName")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Identifier")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("StarId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("StarId");
|
|
||||||
|
|
||||||
b.ToTable("Servers_StarDockerImages", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<bool>("AllowEditing")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<bool>("AllowViewing")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<string>("DefaultValue")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Filter")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Key")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("StarId")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("Type")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("StarId");
|
|
||||||
|
|
||||||
b.ToTable("Servers_StarVariables", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
|
|
||||||
.WithMany("Allocations")
|
|
||||||
.HasForeignKey("NodeId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
|
|
||||||
.WithMany("Allocations")
|
|
||||||
.HasForeignKey("ServerId");
|
|
||||||
|
|
||||||
b.Navigation("Node");
|
|
||||||
|
|
||||||
b.Navigation("Server");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
|
|
||||||
.WithMany("Servers")
|
|
||||||
.HasForeignKey("NodeId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("StarId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Node");
|
|
||||||
|
|
||||||
b.Navigation("Star");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", null)
|
|
||||||
.WithMany("Backups")
|
|
||||||
.HasForeignKey("ServerId");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
|
|
||||||
.WithMany("Variables")
|
|
||||||
.HasForeignKey("ServerId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Server");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
|
|
||||||
.WithMany("DockerImages")
|
|
||||||
.HasForeignKey("StarId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Star");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
|
|
||||||
.WithMany("Variables")
|
|
||||||
.HasForeignKey("StarId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Star");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Allocations");
|
|
||||||
|
|
||||||
b.Navigation("Servers");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Allocations");
|
|
||||||
|
|
||||||
b.Navigation("Backups");
|
|
||||||
|
|
||||||
b.Navigation("Variables");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("DockerImages");
|
|
||||||
|
|
||||||
b.Navigation("Variables");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Database.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class AddedTokenIdField : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "TokenId",
|
|
||||||
table: "Servers_Nodes",
|
|
||||||
type: "text",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "TokenId",
|
|
||||||
table: "Servers_Nodes");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Database.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class AddedShares : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Servers_ServerShares",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<int>(type: "integer", nullable: false)
|
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
|
||||||
UserId = table.Column<int>(type: "integer", nullable: false),
|
|
||||||
ServerId = table.Column<int>(type: "integer", nullable: false),
|
|
||||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
|
||||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
|
||||||
Content = table.Column<string>(type: "jsonb", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_Servers_ServerShares", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Servers_ServerShares_Servers_Servers_ServerId",
|
|
||||||
column: x => x.ServerId,
|
|
||||||
principalTable: "Servers_Servers",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Servers_ServerShares_ServerId",
|
|
||||||
table: "Servers_ServerShares",
|
|
||||||
column: "ServerId");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Servers_ServerShares");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,15 +12,16 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace MoonlightServers.ApiServer.Database.Migrations
|
namespace MoonlightServers.ApiServer.Database.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(ServersDataContext))]
|
[DbContext(typeof(ServersDataContext))]
|
||||||
[Migration("20250606121013_AddedShares")]
|
[Migration("20250922091731_RecreatedModelsInNewSchema")]
|
||||||
partial class AddedShares
|
partial class RecreatedModelsInNewSchema
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "9.0.5")
|
.HasDefaultSchema("servers")
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.9")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
@@ -52,7 +53,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("ServerId");
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
b.ToTable("Servers_Allocations", (string)null);
|
b.ToTable("Allocations", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
||||||
@@ -63,12 +64,6 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
b.Property<bool>("EnableDynamicFirewall")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<bool>("EnableTransparentMode")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<string>("Fqdn")
|
b.Property<string>("Fqdn")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
@@ -96,7 +91,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("Servers_Nodes", (string)null);
|
b.ToTable("Nodes", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
||||||
@@ -107,9 +102,6 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
b.Property<int>("Bandwidth")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("Cpu")
|
b.Property<int>("Cpu")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
@@ -138,16 +130,13 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
b.Property<string>("StartupOverride")
|
b.Property<string>("StartupOverride")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<bool>("UseVirtualDisk")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("NodeId");
|
b.HasIndex("NodeId");
|
||||||
|
|
||||||
b.HasIndex("StarId");
|
b.HasIndex("StarId");
|
||||||
|
|
||||||
b.ToTable("Servers_Servers", (string)null);
|
b.ToTable("Servers", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
|
||||||
@@ -180,7 +169,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("ServerId");
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
b.ToTable("Servers_ServerBackups", (string)null);
|
b.ToTable("ServerBackups", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b =>
|
||||||
@@ -207,7 +196,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("ServerId");
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
b.ToTable("Servers_ServerShares", (string)null);
|
b.ToTable("ServerShares", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
|
||||||
@@ -233,7 +222,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("ServerId");
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
b.ToTable("Servers_ServerVariables", (string)null);
|
b.ToTable("ServerVariables", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
|
||||||
@@ -301,7 +290,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("Servers_Stars", (string)null);
|
b.ToTable("Stars", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
|
||||||
@@ -330,7 +319,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("StarId");
|
b.HasIndex("StarId");
|
||||||
|
|
||||||
b.ToTable("Servers_StarDockerImages", (string)null);
|
b.ToTable("StarDockerImages", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
|
||||||
@@ -376,7 +365,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("StarId");
|
b.HasIndex("StarId");
|
||||||
|
|
||||||
b.ToTable("Servers_StarVariables", (string)null);
|
b.ToTable("StarVariables", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
|
||||||
@@ -437,14 +426,14 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b1.HasKey("ServerShareId");
|
b1.HasKey("ServerShareId");
|
||||||
|
|
||||||
b1.ToTable("Servers_ServerShares");
|
b1.ToTable("ServerShares", "servers");
|
||||||
|
|
||||||
b1.ToJson("Content");
|
b1.ToJson("Content");
|
||||||
|
|
||||||
b1.WithOwner()
|
b1.WithOwner()
|
||||||
.HasForeignKey("ServerShareId");
|
.HasForeignKey("ServerShareId");
|
||||||
|
|
||||||
b1.OwnsMany("MoonlightServers.ApiServer.Models.ServerSharePermission", "Permissions", b2 =>
|
b1.OwnsMany("MoonlightServers.ApiServer.Models.ServerShareContent+SharePermission", "Permissions", b2 =>
|
||||||
{
|
{
|
||||||
b2.Property<int>("ServerShareContentServerShareId")
|
b2.Property<int>("ServerShareContentServerShareId")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -453,16 +442,16 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b2.Property<string>("Name")
|
b2.Property<string>("Identifier")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b2.Property<int>("Type")
|
b2.Property<int>("Level")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b2.HasKey("ServerShareContentServerShareId", "__synthesizedOrdinal");
|
b2.HasKey("ServerShareContentServerShareId", "__synthesizedOrdinal");
|
||||||
|
|
||||||
b2.ToTable("Servers_ServerShares");
|
b2.ToTable("ServerShares", "servers");
|
||||||
|
|
||||||
b2.WithOwner()
|
b2.WithOwner()
|
||||||
.HasForeignKey("ServerShareContentServerShareId");
|
.HasForeignKey("ServerShareContentServerShareId");
|
||||||
@@ -7,13 +7,17 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace MoonlightServers.ApiServer.Database.Migrations
|
namespace MoonlightServers.ApiServer.Database.Migrations
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class RecreatedMigrationsForPostgresql : Migration
|
public partial class RecreatedModelsInNewSchema : Migration
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
|
migrationBuilder.EnsureSchema(
|
||||||
|
name: "servers");
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Servers_Nodes",
|
name: "Nodes",
|
||||||
|
schema: "servers",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<int>(type: "integer", nullable: false)
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
@@ -21,19 +25,19 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
Name = table.Column<string>(type: "text", nullable: false),
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
Fqdn = table.Column<string>(type: "text", nullable: false),
|
Fqdn = table.Column<string>(type: "text", nullable: false),
|
||||||
Token = table.Column<string>(type: "text", nullable: false),
|
Token = table.Column<string>(type: "text", nullable: false),
|
||||||
|
TokenId = table.Column<string>(type: "text", nullable: false),
|
||||||
HttpPort = table.Column<int>(type: "integer", nullable: false),
|
HttpPort = table.Column<int>(type: "integer", nullable: false),
|
||||||
FtpPort = table.Column<int>(type: "integer", nullable: false),
|
FtpPort = table.Column<int>(type: "integer", nullable: false),
|
||||||
UseSsl = table.Column<bool>(type: "boolean", nullable: false),
|
UseSsl = table.Column<bool>(type: "boolean", nullable: false)
|
||||||
EnableTransparentMode = table.Column<bool>(type: "boolean", nullable: false),
|
|
||||||
EnableDynamicFirewall = table.Column<bool>(type: "boolean", nullable: false)
|
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Servers_Nodes", x => x.Id);
|
table.PrimaryKey("PK_Nodes", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Servers_Stars",
|
name: "Stars",
|
||||||
|
schema: "servers",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<int>(type: "integer", nullable: false)
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
@@ -56,11 +60,12 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Servers_Stars", x => x.Id);
|
table.PrimaryKey("PK_Stars", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Servers_Servers",
|
name: "Servers",
|
||||||
|
schema: "servers",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<int>(type: "integer", nullable: false)
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
@@ -73,29 +78,30 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
DockerImageIndex = table.Column<int>(type: "integer", nullable: false),
|
DockerImageIndex = table.Column<int>(type: "integer", nullable: false),
|
||||||
Cpu = table.Column<int>(type: "integer", nullable: false),
|
Cpu = table.Column<int>(type: "integer", nullable: false),
|
||||||
Memory = table.Column<int>(type: "integer", nullable: false),
|
Memory = table.Column<int>(type: "integer", nullable: false),
|
||||||
Disk = table.Column<int>(type: "integer", nullable: false),
|
Disk = table.Column<int>(type: "integer", nullable: false)
|
||||||
UseVirtualDisk = table.Column<bool>(type: "boolean", nullable: false),
|
|
||||||
Bandwidth = table.Column<int>(type: "integer", nullable: false)
|
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Servers_Servers", x => x.Id);
|
table.PrimaryKey("PK_Servers", x => x.Id);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Servers_Servers_Servers_Nodes_NodeId",
|
name: "FK_Servers_Nodes_NodeId",
|
||||||
column: x => x.NodeId,
|
column: x => x.NodeId,
|
||||||
principalTable: "Servers_Nodes",
|
principalSchema: "servers",
|
||||||
|
principalTable: "Nodes",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Servers_Servers_Servers_Stars_StarId",
|
name: "FK_Servers_Stars_StarId",
|
||||||
column: x => x.StarId,
|
column: x => x.StarId,
|
||||||
principalTable: "Servers_Stars",
|
principalSchema: "servers",
|
||||||
|
principalTable: "Stars",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Servers_StarDockerImages",
|
name: "StarDockerImages",
|
||||||
|
schema: "servers",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<int>(type: "integer", nullable: false)
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
@@ -107,17 +113,19 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Servers_StarDockerImages", x => x.Id);
|
table.PrimaryKey("PK_StarDockerImages", x => x.Id);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Servers_StarDockerImages_Servers_Stars_StarId",
|
name: "FK_StarDockerImages_Stars_StarId",
|
||||||
column: x => x.StarId,
|
column: x => x.StarId,
|
||||||
principalTable: "Servers_Stars",
|
principalSchema: "servers",
|
||||||
|
principalTable: "Stars",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Servers_StarVariables",
|
name: "StarVariables",
|
||||||
|
schema: "servers",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<int>(type: "integer", nullable: false)
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
@@ -134,17 +142,19 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Servers_StarVariables", x => x.Id);
|
table.PrimaryKey("PK_StarVariables", x => x.Id);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Servers_StarVariables_Servers_Stars_StarId",
|
name: "FK_StarVariables_Stars_StarId",
|
||||||
column: x => x.StarId,
|
column: x => x.StarId,
|
||||||
principalTable: "Servers_Stars",
|
principalSchema: "servers",
|
||||||
|
principalTable: "Stars",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Servers_Allocations",
|
name: "Allocations",
|
||||||
|
schema: "servers",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<int>(type: "integer", nullable: false)
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
@@ -156,22 +166,25 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Servers_Allocations", x => x.Id);
|
table.PrimaryKey("PK_Allocations", x => x.Id);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Servers_Allocations_Servers_Nodes_NodeId",
|
name: "FK_Allocations_Nodes_NodeId",
|
||||||
column: x => x.NodeId,
|
column: x => x.NodeId,
|
||||||
principalTable: "Servers_Nodes",
|
principalSchema: "servers",
|
||||||
|
principalTable: "Nodes",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Servers_Allocations_Servers_Servers_ServerId",
|
name: "FK_Allocations_Servers_ServerId",
|
||||||
column: x => x.ServerId,
|
column: x => x.ServerId,
|
||||||
principalTable: "Servers_Servers",
|
principalSchema: "servers",
|
||||||
|
principalTable: "Servers",
|
||||||
principalColumn: "Id");
|
principalColumn: "Id");
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Servers_ServerBackups",
|
name: "ServerBackups",
|
||||||
|
schema: "servers",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<int>(type: "integer", nullable: false)
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
@@ -185,16 +198,43 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Servers_ServerBackups", x => x.Id);
|
table.PrimaryKey("PK_ServerBackups", x => x.Id);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Servers_ServerBackups_Servers_Servers_ServerId",
|
name: "FK_ServerBackups_Servers_ServerId",
|
||||||
column: x => x.ServerId,
|
column: x => x.ServerId,
|
||||||
principalTable: "Servers_Servers",
|
principalSchema: "servers",
|
||||||
|
principalTable: "Servers",
|
||||||
principalColumn: "Id");
|
principalColumn: "Id");
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Servers_ServerVariables",
|
name: "ServerShares",
|
||||||
|
schema: "servers",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
UserId = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
ServerId = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
Content = table.Column<string>(type: "jsonb", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ServerShares", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ServerShares_Servers_ServerId",
|
||||||
|
column: x => x.ServerId,
|
||||||
|
principalSchema: "servers",
|
||||||
|
principalTable: "Servers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ServerVariables",
|
||||||
|
schema: "servers",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<int>(type: "integer", nullable: false)
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
@@ -205,53 +245,68 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Servers_ServerVariables", x => x.Id);
|
table.PrimaryKey("PK_ServerVariables", x => x.Id);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Servers_ServerVariables_Servers_Servers_ServerId",
|
name: "FK_ServerVariables_Servers_ServerId",
|
||||||
column: x => x.ServerId,
|
column: x => x.ServerId,
|
||||||
principalTable: "Servers_Servers",
|
principalSchema: "servers",
|
||||||
|
principalTable: "Servers",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Servers_Allocations_NodeId",
|
name: "IX_Allocations_NodeId",
|
||||||
table: "Servers_Allocations",
|
schema: "servers",
|
||||||
|
table: "Allocations",
|
||||||
column: "NodeId");
|
column: "NodeId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Servers_Allocations_ServerId",
|
name: "IX_Allocations_ServerId",
|
||||||
table: "Servers_Allocations",
|
schema: "servers",
|
||||||
|
table: "Allocations",
|
||||||
column: "ServerId");
|
column: "ServerId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Servers_ServerBackups_ServerId",
|
name: "IX_ServerBackups_ServerId",
|
||||||
table: "Servers_ServerBackups",
|
schema: "servers",
|
||||||
|
table: "ServerBackups",
|
||||||
column: "ServerId");
|
column: "ServerId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Servers_Servers_NodeId",
|
name: "IX_Servers_NodeId",
|
||||||
table: "Servers_Servers",
|
schema: "servers",
|
||||||
|
table: "Servers",
|
||||||
column: "NodeId");
|
column: "NodeId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Servers_Servers_StarId",
|
name: "IX_Servers_StarId",
|
||||||
table: "Servers_Servers",
|
schema: "servers",
|
||||||
|
table: "Servers",
|
||||||
column: "StarId");
|
column: "StarId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Servers_ServerVariables_ServerId",
|
name: "IX_ServerShares_ServerId",
|
||||||
table: "Servers_ServerVariables",
|
schema: "servers",
|
||||||
|
table: "ServerShares",
|
||||||
column: "ServerId");
|
column: "ServerId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Servers_StarDockerImages_StarId",
|
name: "IX_ServerVariables_ServerId",
|
||||||
table: "Servers_StarDockerImages",
|
schema: "servers",
|
||||||
|
table: "ServerVariables",
|
||||||
|
column: "ServerId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_StarDockerImages_StarId",
|
||||||
|
schema: "servers",
|
||||||
|
table: "StarDockerImages",
|
||||||
column: "StarId");
|
column: "StarId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Servers_StarVariables_StarId",
|
name: "IX_StarVariables_StarId",
|
||||||
table: "Servers_StarVariables",
|
schema: "servers",
|
||||||
|
table: "StarVariables",
|
||||||
column: "StarId");
|
column: "StarId");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,28 +314,40 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Servers_Allocations");
|
name: "Allocations",
|
||||||
|
schema: "servers");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Servers_ServerBackups");
|
name: "ServerBackups",
|
||||||
|
schema: "servers");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Servers_ServerVariables");
|
name: "ServerShares",
|
||||||
|
schema: "servers");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Servers_StarDockerImages");
|
name: "ServerVariables",
|
||||||
|
schema: "servers");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Servers_StarVariables");
|
name: "StarDockerImages",
|
||||||
|
schema: "servers");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Servers_Servers");
|
name: "StarVariables",
|
||||||
|
schema: "servers");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Servers_Nodes");
|
name: "Servers",
|
||||||
|
schema: "servers");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Servers_Stars");
|
name: "Nodes",
|
||||||
|
schema: "servers");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Stars",
|
||||||
|
schema: "servers");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,8 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "9.0.5")
|
.HasDefaultSchema("servers")
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.9")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
@@ -49,7 +50,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("ServerId");
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
b.ToTable("Servers_Allocations", (string)null);
|
b.ToTable("Allocations", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
||||||
@@ -60,12 +61,6 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
b.Property<bool>("EnableDynamicFirewall")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<bool>("EnableTransparentMode")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<string>("Fqdn")
|
b.Property<string>("Fqdn")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
@@ -93,7 +88,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("Servers_Nodes", (string)null);
|
b.ToTable("Nodes", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
||||||
@@ -104,9 +99,6 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
b.Property<int>("Bandwidth")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("Cpu")
|
b.Property<int>("Cpu")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
@@ -135,16 +127,13 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
b.Property<string>("StartupOverride")
|
b.Property<string>("StartupOverride")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<bool>("UseVirtualDisk")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("NodeId");
|
b.HasIndex("NodeId");
|
||||||
|
|
||||||
b.HasIndex("StarId");
|
b.HasIndex("StarId");
|
||||||
|
|
||||||
b.ToTable("Servers_Servers", (string)null);
|
b.ToTable("Servers", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
|
||||||
@@ -177,7 +166,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("ServerId");
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
b.ToTable("Servers_ServerBackups", (string)null);
|
b.ToTable("ServerBackups", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b =>
|
||||||
@@ -204,7 +193,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("ServerId");
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
b.ToTable("Servers_ServerShares", (string)null);
|
b.ToTable("ServerShares", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
|
||||||
@@ -230,7 +219,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("ServerId");
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
b.ToTable("Servers_ServerVariables", (string)null);
|
b.ToTable("ServerVariables", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
|
||||||
@@ -298,7 +287,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("Servers_Stars", (string)null);
|
b.ToTable("Stars", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
|
||||||
@@ -327,7 +316,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("StarId");
|
b.HasIndex("StarId");
|
||||||
|
|
||||||
b.ToTable("Servers_StarDockerImages", (string)null);
|
b.ToTable("StarDockerImages", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
|
||||||
@@ -373,7 +362,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("StarId");
|
b.HasIndex("StarId");
|
||||||
|
|
||||||
b.ToTable("Servers_StarVariables", (string)null);
|
b.ToTable("StarVariables", "servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
|
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
|
||||||
@@ -434,14 +423,14 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
|
|
||||||
b1.HasKey("ServerShareId");
|
b1.HasKey("ServerShareId");
|
||||||
|
|
||||||
b1.ToTable("Servers_ServerShares");
|
b1.ToTable("ServerShares", "servers");
|
||||||
|
|
||||||
b1.ToJson("Content");
|
b1.ToJson("Content");
|
||||||
|
|
||||||
b1.WithOwner()
|
b1.WithOwner()
|
||||||
.HasForeignKey("ServerShareId");
|
.HasForeignKey("ServerShareId");
|
||||||
|
|
||||||
b1.OwnsMany("MoonlightServers.ApiServer.Models.ServerSharePermission", "Permissions", b2 =>
|
b1.OwnsMany("MoonlightServers.ApiServer.Models.ServerShareContent+SharePermission", "Permissions", b2 =>
|
||||||
{
|
{
|
||||||
b2.Property<int>("ServerShareContentServerShareId")
|
b2.Property<int>("ServerShareContentServerShareId")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -450,16 +439,16 @@ namespace MoonlightServers.ApiServer.Database.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b2.Property<string>("Name")
|
b2.Property<string>("Identifier")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b2.Property<int>("Type")
|
b2.Property<int>("Level")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b2.HasKey("ServerShareContentServerShareId", "__synthesizedOrdinal");
|
b2.HasKey("ServerShareContentServerShareId", "__synthesizedOrdinal");
|
||||||
|
|
||||||
b2.ToTable("Servers_ServerShares");
|
b2.ToTable("ServerShares", "servers");
|
||||||
|
|
||||||
b2.WithOwner()
|
b2.WithOwner()
|
||||||
.HasForeignKey("ServerShareContentServerShareId");
|
.HasForeignKey("ServerShareContentServerShareId");
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonCore.Extended.SingleDb;
|
|
||||||
using Moonlight.ApiServer.Configuration;
|
using Moonlight.ApiServer.Configuration;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Models;
|
using MoonlightServers.ApiServer.Models;
|
||||||
using MoonlightServers.Shared.Models;
|
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Database;
|
namespace MoonlightServers.ApiServer.Database;
|
||||||
|
|
||||||
public class ServersDataContext : DatabaseContext
|
public class ServersDataContext : DbContext
|
||||||
{
|
{
|
||||||
public override string Prefix { get; } = "Servers";
|
|
||||||
|
|
||||||
public DbSet<Allocation> Allocations { get; set; }
|
public DbSet<Allocation> Allocations { get; set; }
|
||||||
public DbSet<Node> Nodes { get; set; }
|
public DbSet<Node> Nodes { get; set; }
|
||||||
public DbSet<Server> Servers { get; set; }
|
public DbSet<Server> Servers { get; set; }
|
||||||
@@ -21,33 +17,50 @@ public class ServersDataContext : DatabaseContext
|
|||||||
public DbSet<StarDockerImage> StarDockerImages { get; set; }
|
public DbSet<StarDockerImage> StarDockerImages { get; set; }
|
||||||
public DbSet<StarVariable> StarVariables { get; set; }
|
public DbSet<StarVariable> StarVariables { get; set; }
|
||||||
|
|
||||||
|
private readonly AppConfiguration Configuration;
|
||||||
|
private readonly string Schema = "servers";
|
||||||
|
|
||||||
public ServersDataContext(AppConfiguration configuration)
|
public ServersDataContext(AppConfiguration configuration)
|
||||||
{
|
{
|
||||||
Options = new()
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
|
{
|
||||||
|
if(optionsBuilder.IsConfigured)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var database = Configuration.Database;
|
||||||
|
|
||||||
|
var connectionString = $"Host={database.Host};" +
|
||||||
|
$"Port={database.Port};" +
|
||||||
|
$"Database={database.Database};" +
|
||||||
|
$"Username={database.Username};" +
|
||||||
|
$"Password={database.Password}";
|
||||||
|
|
||||||
|
optionsBuilder.UseNpgsql(connectionString, builder =>
|
||||||
{
|
{
|
||||||
Host = configuration.Database.Host,
|
builder.MigrationsHistoryTable("MigrationsHistory", Schema);
|
||||||
Port = configuration.Database.Port,
|
});
|
||||||
Username = configuration.Database.Username,
|
|
||||||
Password = configuration.Database.Password,
|
|
||||||
Database = configuration.Database.Database
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
modelBuilder.Model.SetDefaultSchema(Schema);
|
||||||
|
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|
||||||
#region Shares
|
#region Shares
|
||||||
|
|
||||||
modelBuilder.Ignore<ServerShareContent>();
|
modelBuilder.Ignore<ServerShareContent>();
|
||||||
modelBuilder.Ignore<ServerSharePermission>();
|
modelBuilder.Ignore<ServerShareContent.SharePermission>();
|
||||||
|
|
||||||
modelBuilder.Entity<ServerShare>(builder =>
|
modelBuilder.Entity<ServerShare>(builder =>
|
||||||
{
|
{
|
||||||
builder.OwnsOne(x => x.Content, navigationBuilder =>
|
builder.OwnsOne(x => x.Content, navigationBuilder =>
|
||||||
{
|
{
|
||||||
navigationBuilder.ToJson();
|
navigationBuilder.ToJson();
|
||||||
|
|
||||||
navigationBuilder.OwnsMany(x => x.Permissions);
|
navigationBuilder.OwnsMany(x => x.Permissions);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using MoonlightServers.DaemonShared.Enums;
|
|
||||||
using MoonlightServers.Shared.Enums;
|
|
||||||
using ServerState = MoonlightServers.Shared.Enums.ServerState;
|
using ServerState = MoonlightServers.Shared.Enums.ServerState;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Extensions;
|
namespace MoonlightServers.ApiServer.Extensions;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ using System.Security.Claims;
|
|||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonCore.Exceptions;
|
using MoonCore.Common;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Models;
|
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.ApiServer.Mappers;
|
||||||
using MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations;
|
using MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations;
|
||||||
using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations;
|
using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
|
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/admin/servers/nodes")]
|
[Route("api/admin/servers/nodes/{nodeId:int}/allocations")]
|
||||||
public class NodeAllocationsController : Controller
|
public class NodeAllocationsController : Controller
|
||||||
{
|
{
|
||||||
private readonly DatabaseRepository<Node> NodeRepository;
|
private readonly DatabaseRepository<Node> NodeRepository;
|
||||||
@@ -27,63 +26,58 @@ public class NodeAllocationsController : Controller
|
|||||||
AllocationRepository = allocationRepository;
|
AllocationRepository = allocationRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{nodeId:int}/allocations")]
|
[HttpGet]
|
||||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||||
public async Task<IPagedData<NodeAllocationDetailResponse>> Get(
|
public async Task<ActionResult<CountedData<NodeAllocationResponse>>> GetAsync(
|
||||||
[FromRoute] int nodeId,
|
[FromRoute] int nodeId,
|
||||||
[FromQuery] int page,
|
[FromQuery] int startIndex,
|
||||||
[FromQuery] [Range(1, 100)] int pageSize
|
[FromQuery] int count
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var count = await AllocationRepository.Get().CountAsync(x => x.Node.Id == nodeId);
|
if (count > 100)
|
||||||
|
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||||
|
|
||||||
|
var totalCount = await AllocationRepository
|
||||||
|
.Get()
|
||||||
|
.CountAsync(x => x.Node.Id == nodeId);
|
||||||
|
|
||||||
var allocations = await AllocationRepository
|
var allocations = await AllocationRepository
|
||||||
.Get()
|
.Get()
|
||||||
.Skip(page * pageSize)
|
.OrderBy(x => x.Id)
|
||||||
.Take(pageSize)
|
.Skip(startIndex)
|
||||||
|
.Take(count)
|
||||||
.Where(x => x.Node.Id == nodeId)
|
.Where(x => x.Node.Id == nodeId)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ProjectToAdminResponse()
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
|
|
||||||
var mappedAllocations = allocations.Select(x => new NodeAllocationDetailResponse()
|
return new CountedData<NodeAllocationResponse>()
|
||||||
{
|
{
|
||||||
Id = x.Id,
|
Items = allocations,
|
||||||
IpAddress = x.IpAddress,
|
TotalCount = totalCount
|
||||||
Port = x.Port
|
|
||||||
}).ToArray();
|
|
||||||
|
|
||||||
return new PagedData<NodeAllocationDetailResponse>()
|
|
||||||
{
|
|
||||||
Items = mappedAllocations,
|
|
||||||
CurrentPage = page,
|
|
||||||
PageSize = pageSize,
|
|
||||||
TotalItems = count,
|
|
||||||
TotalPages = count == 0 ? 0 : (count - 1) / pageSize
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{nodeId:int}/allocations/{id:int}")]
|
[HttpGet("{id:int}")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||||
public async Task<NodeAllocationDetailResponse> GetSingle([FromRoute] int nodeId, [FromRoute] int id)
|
public async Task<ActionResult<NodeAllocationResponse>> GetSingleAsync([FromRoute] int nodeId, [FromRoute] int id)
|
||||||
{
|
{
|
||||||
var allocation = await AllocationRepository
|
var allocation = await AllocationRepository
|
||||||
.Get()
|
.Get()
|
||||||
.Where(x => x.Node.Id == nodeId)
|
.Where(x => x.Node.Id == nodeId)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ProjectToAdminResponse()
|
||||||
.FirstOrDefaultAsync(x => x.Id == id);
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
if (allocation == null)
|
if (allocation == null)
|
||||||
throw new HttpApiException("No allocation with that id found", 404);
|
return Problem("No allocation with that id found", statusCode: 400);
|
||||||
|
|
||||||
return new()
|
return allocation;
|
||||||
{
|
|
||||||
Id = allocation.Id,
|
|
||||||
IpAddress = allocation.IpAddress,
|
|
||||||
Port = allocation.Port
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{nodeId:int}/allocations")]
|
[HttpPost]
|
||||||
[Authorize(Policy = "permissions:admin.servers.nodes.create")]
|
[Authorize(Policy = "permissions:admin.servers.nodes.create")]
|
||||||
public async Task<NodeAllocationDetailResponse> Create(
|
public async Task<ActionResult<NodeAllocationResponse>> CreateAsync(
|
||||||
[FromRoute] int nodeId,
|
[FromRoute] int nodeId,
|
||||||
[FromBody] CreateNodeAllocationRequest request
|
[FromBody] CreateNodeAllocationRequest request
|
||||||
)
|
)
|
||||||
@@ -93,28 +87,21 @@ public class NodeAllocationsController : Controller
|
|||||||
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
||||||
|
|
||||||
if (node == null)
|
if (node == null)
|
||||||
throw new HttpApiException("No node with that id found", 404);
|
return Problem("No node with that id found", statusCode: 404);
|
||||||
|
|
||||||
var allocation = new Allocation
|
var allocation = AllocationMapper.ToAllocation(request);
|
||||||
{
|
|
||||||
IpAddress = request.IpAddress,
|
|
||||||
Port = request.Port,
|
|
||||||
Node = node
|
|
||||||
};
|
|
||||||
|
|
||||||
var finalVariable = await AllocationRepository.Add(allocation);
|
var finalAllocation = await AllocationRepository.AddAsync(allocation);
|
||||||
|
|
||||||
return new()
|
return AllocationMapper.ToNodeAllocation(finalAllocation);
|
||||||
{
|
|
||||||
Id = finalVariable.Id,
|
|
||||||
IpAddress = finalVariable.IpAddress,
|
|
||||||
Port = finalVariable.Port
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPatch("{nodeId:int}/allocations/{id:int}")]
|
[HttpPatch("{id:int}")]
|
||||||
public async Task<NodeAllocationDetailResponse> Update([FromRoute] int nodeId, [FromRoute] int id,
|
public async Task<ActionResult<NodeAllocationResponse>> UpdateAsync(
|
||||||
[FromBody] UpdateNodeAllocationRequest request)
|
[FromRoute] int nodeId,
|
||||||
|
[FromRoute] int id,
|
||||||
|
[FromBody] UpdateNodeAllocationRequest request
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var allocation = await AllocationRepository
|
var allocation = await AllocationRepository
|
||||||
.Get()
|
.Get()
|
||||||
@@ -122,23 +109,16 @@ public class NodeAllocationsController : Controller
|
|||||||
.FirstOrDefaultAsync(x => x.Id == id);
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
if (allocation == null)
|
if (allocation == null)
|
||||||
throw new HttpApiException("No allocation with that id found", 404);
|
return Problem("No allocation with that id found", statusCode: 404);
|
||||||
|
|
||||||
allocation.IpAddress = request.IpAddress;
|
AllocationMapper.Merge(request, allocation);
|
||||||
allocation.Port = request.Port;
|
await AllocationRepository.UpdateAsync(allocation);
|
||||||
|
|
||||||
await AllocationRepository.Update(allocation);
|
return AllocationMapper.ToNodeAllocation(allocation);
|
||||||
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
Id = allocation.Id,
|
|
||||||
IpAddress = allocation.IpAddress,
|
|
||||||
Port = allocation.Port
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{nodeId:int}/allocations/{id:int}")]
|
[HttpDelete("{id:int}")]
|
||||||
public async Task Delete([FromRoute] int nodeId, [FromRoute] int id)
|
public async Task<ActionResult> DeleteAsync([FromRoute] int nodeId, [FromRoute] int id)
|
||||||
{
|
{
|
||||||
var allocation = await AllocationRepository
|
var allocation = await AllocationRepository
|
||||||
.Get()
|
.Get()
|
||||||
@@ -146,32 +126,44 @@ public class NodeAllocationsController : Controller
|
|||||||
.FirstOrDefaultAsync(x => x.Id == id);
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
if (allocation == null)
|
if (allocation == null)
|
||||||
throw new HttpApiException("No allocation with that id found", 404);
|
return Problem("No allocation with that id found", statusCode: 404);
|
||||||
|
|
||||||
await AllocationRepository.Remove(allocation);
|
await AllocationRepository.RemoveAsync(allocation);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{nodeId:int}/allocations/range")]
|
[HttpPost("range")]
|
||||||
public async Task CreateRange([FromRoute] int nodeId, [FromBody] CreateNodeAllocationRangeRequest rangeRequest)
|
public async Task<ActionResult> CreateRangeAsync(
|
||||||
|
[FromRoute] int nodeId,
|
||||||
|
[FromBody] CreateNodeAllocationRangeRequest request
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
if (request.Start > request.End)
|
||||||
|
return Problem("Invalid start and end specified", statusCode: 400);
|
||||||
|
|
||||||
|
if (request.End - request.Start == 0)
|
||||||
|
return Problem("Empty range specified", statusCode: 400);
|
||||||
|
|
||||||
var node = await NodeRepository
|
var node = await NodeRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
||||||
|
|
||||||
if (node == null)
|
if (node == null)
|
||||||
throw new HttpApiException("No node with that id found", 404);
|
return Problem("No node with that id found", statusCode: 404);
|
||||||
|
|
||||||
var existingAllocations = AllocationRepository
|
var existingAllocations = await AllocationRepository
|
||||||
.Get()
|
.Get()
|
||||||
.Where(x => x.Node.Id == nodeId)
|
.Where(x => x.Port >= request.Start && x.Port <= request.End &&
|
||||||
.ToArray();
|
x.IpAddress == request.IpAddress)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
var ports = new List<int>();
|
var ports = new List<int>();
|
||||||
|
|
||||||
for (var i = rangeRequest.Start; i < rangeRequest.End; i++)
|
for (var i = request.Start; i < request.End; i++)
|
||||||
{
|
{
|
||||||
// Skip existing allocations
|
// Skip existing allocations
|
||||||
if (existingAllocations.Any(x => x.Port == i && x.IpAddress == rangeRequest.IpAddress))
|
if (existingAllocations.Any(x => x.Port == i))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ports.Add(i);
|
ports.Add(i);
|
||||||
@@ -180,60 +172,66 @@ public class NodeAllocationsController : Controller
|
|||||||
var allocations = ports
|
var allocations = ports
|
||||||
.Select(port => new Allocation()
|
.Select(port => new Allocation()
|
||||||
{
|
{
|
||||||
IpAddress = rangeRequest.IpAddress,
|
IpAddress = request.IpAddress,
|
||||||
Port = port,
|
Port = port,
|
||||||
Node = node
|
Node = node
|
||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
await AllocationRepository.RunTransaction(async set => { await set.AddRangeAsync(allocations); });
|
await AllocationRepository.RunTransactionAsync(async set => { await set.AddRangeAsync(allocations); });
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{nodeId:int}/allocations/all")]
|
[HttpDelete("all")]
|
||||||
public async Task DeleteAll([FromRoute] int nodeId)
|
public async Task<ActionResult> DeleteAllAsync([FromRoute] int nodeId)
|
||||||
{
|
{
|
||||||
var allocations = AllocationRepository
|
var allocations = AllocationRepository
|
||||||
.Get()
|
.Get()
|
||||||
.Where(x => x.Node.Id == nodeId)
|
.Where(x => x.Node.Id == nodeId)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
await AllocationRepository.RunTransaction(set => { set.RemoveRange(allocations); });
|
await AllocationRepository.RunTransactionAsync(set => { set.RemoveRange(allocations); });
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{nodeId:int}/allocations/free")]
|
[HttpGet("free")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||||
public async Task<IPagedData<NodeAllocationDetailResponse>> GetFree([FromRoute] int nodeId, [FromQuery] int page,
|
public async Task<ActionResult<CountedData<NodeAllocationResponse>>> GetFreeAsync(
|
||||||
[FromQuery][Range(1, 100)] int pageSize, [FromQuery] int serverId = -1)
|
[FromRoute] int nodeId,
|
||||||
|
[FromQuery] int startIndex,
|
||||||
|
[FromQuery] int count,
|
||||||
|
[FromQuery] int serverId = -1
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
if (count > 100)
|
||||||
|
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||||
|
|
||||||
var node = NodeRepository
|
var node = NodeRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefault(x => x.Id == nodeId);
|
.FirstOrDefault(x => x.Id == nodeId);
|
||||||
|
|
||||||
if (node == null)
|
if (node == null)
|
||||||
throw new HttpApiException("A node with this id could not be found", 404);
|
return Problem("A node with this id could not be found", statusCode: 404);
|
||||||
|
|
||||||
var freeAllocationsQuery = AllocationRepository
|
var freeAllocationsQuery = AllocationRepository
|
||||||
.Get()
|
.Get()
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
.Where(x => x.Node.Id == node.Id)
|
.Where(x => x.Node.Id == node.Id)
|
||||||
.Where(x => x.Server == null || x.Server.Id == serverId);
|
.Where(x => x.Server == null || x.Server.Id == serverId);
|
||||||
|
|
||||||
var count = await freeAllocationsQuery.CountAsync();
|
var totalCount = await freeAllocationsQuery.CountAsync();
|
||||||
var allocations = await freeAllocationsQuery.ToArrayAsync();
|
|
||||||
|
|
||||||
var mappedAllocations = allocations.Select(x => new NodeAllocationDetailResponse()
|
var allocations = await freeAllocationsQuery
|
||||||
{
|
.Skip(startIndex)
|
||||||
Id = x.Id,
|
.Take(count)
|
||||||
IpAddress = x.IpAddress,
|
.AsNoTracking()
|
||||||
Port = x.Port
|
.ProjectToAdminResponse()
|
||||||
}).ToArray();
|
.ToArrayAsync();
|
||||||
|
|
||||||
return new PagedData<NodeAllocationDetailResponse>()
|
return new CountedData<NodeAllocationResponse>()
|
||||||
{
|
{
|
||||||
Items = mappedAllocations,
|
Items = allocations,
|
||||||
CurrentPage = page,
|
TotalCount = totalCount
|
||||||
PageSize = pageSize,
|
|
||||||
TotalItems = count,
|
|
||||||
TotalPages = count == 0 ? 0 : (count - 1) / pageSize
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using MoonCore.Exceptions;
|
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Services;
|
using MoonlightServers.ApiServer.Services;
|
||||||
using MoonlightServers.Shared.Http.Responses.Admin.Nodes.Sys;
|
using MoonlightServers.Shared.Http.Responses.Admin.Nodes.Sys;
|
||||||
@@ -22,12 +22,15 @@ public class NodeStatusController : Controller
|
|||||||
NodeService = nodeService;
|
NodeService = nodeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{nodeId}/system/status")]
|
[HttpGet("{nodeId:int}/system/status")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.nodes.status")]
|
[Authorize(Policy = "permissions:admin.servers.nodes.status")]
|
||||||
public async Task<NodeSystemStatusResponse> GetStatus([FromRoute] int nodeId)
|
public async Task<ActionResult<NodeSystemStatusResponse>> GetStatusAsync([FromRoute] int nodeId)
|
||||||
{
|
{
|
||||||
var node = GetNode(nodeId);
|
var node = await GetNodeAsync(nodeId);
|
||||||
|
|
||||||
|
if (node.Value == null)
|
||||||
|
return node.Result ?? Problem("Unable to retrieve node");
|
||||||
|
|
||||||
NodeSystemStatusResponse response;
|
NodeSystemStatusResponse response;
|
||||||
|
|
||||||
var sw = new Stopwatch();
|
var sw = new Stopwatch();
|
||||||
@@ -35,7 +38,7 @@ public class NodeStatusController : Controller
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var statusResponse = await NodeService.GetSystemStatus(node);
|
var statusResponse = await NodeService.GetSystemStatusAsync(node.Value);
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|
||||||
@@ -65,14 +68,14 @@ public class NodeStatusController : Controller
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node GetNode(int nodeId)
|
private async Task<ActionResult<Node>> GetNodeAsync(int nodeId)
|
||||||
{
|
{
|
||||||
var result = NodeRepository
|
var result = await NodeRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefault(x => x.Id == nodeId);
|
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
||||||
|
|
||||||
if (result == null)
|
if (result == null)
|
||||||
throw new HttpApiException("A node with this id could not be found", 404);
|
return Problem("A node with this id could not be found", statusCode: 404);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Extended.Helpers;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using MoonCore.Common;
|
||||||
using MoonCore.Helpers;
|
using MoonCore.Helpers;
|
||||||
using MoonCore.Models;
|
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.ApiServer.Mappers;
|
||||||
using MoonlightServers.Shared.Http.Requests.Admin.Nodes;
|
using MoonlightServers.Shared.Http.Requests.Admin.Nodes;
|
||||||
using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations;
|
|
||||||
using MoonlightServers.Shared.Http.Responses.Admin.Nodes;
|
using MoonlightServers.Shared.Http.Responses.Admin.Nodes;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
|
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
|
||||||
@@ -16,57 +15,100 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
|
|||||||
[Route("api/admin/servers/nodes")]
|
[Route("api/admin/servers/nodes")]
|
||||||
public class NodesController : Controller
|
public class NodesController : Controller
|
||||||
{
|
{
|
||||||
private readonly CrudHelper<Node, NodeDetailResponse> CrudHelper;
|
|
||||||
private readonly DatabaseRepository<Node> NodeRepository;
|
private readonly DatabaseRepository<Node> NodeRepository;
|
||||||
|
|
||||||
public NodesController(
|
public NodesController(DatabaseRepository<Node> nodeRepository)
|
||||||
CrudHelper<Node, NodeDetailResponse> crudHelper,
|
|
||||||
DatabaseRepository<Node> nodeRepository
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
CrudHelper = crudHelper;
|
|
||||||
NodeRepository = nodeRepository;
|
NodeRepository = nodeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||||
public async Task<IPagedData<NodeDetailResponse>> Get([FromQuery] int page, [FromQuery] int pageSize)
|
public async Task<ActionResult<CountedData<NodeResponse>>> GetAsync(
|
||||||
|
[FromQuery] int startIndex,
|
||||||
|
[FromQuery] int count
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return await CrudHelper.Get(page, pageSize);
|
if (count > 100)
|
||||||
|
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||||
|
|
||||||
|
var totalCount = await NodeRepository.Get().CountAsync();
|
||||||
|
|
||||||
|
var items = await NodeRepository
|
||||||
|
.Get()
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.Skip(startIndex)
|
||||||
|
.Take(count)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ProjectToAdminResponse()
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
return new CountedData<NodeResponse>()
|
||||||
|
{
|
||||||
|
Items = items,
|
||||||
|
TotalCount = totalCount
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id:int}")]
|
[HttpGet("{id:int}")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||||
public async Task<NodeDetailResponse> GetSingle([FromRoute] int id)
|
public async Task<ActionResult<NodeResponse>> GetSingleAsync([FromRoute] int id)
|
||||||
{
|
{
|
||||||
return await CrudHelper.GetSingle(id);
|
var node = await NodeRepository
|
||||||
|
.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.ProjectToAdminResponse()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
return Problem("No node with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize(Policy = "permissions:admin.servers.nodes.create")]
|
[Authorize(Policy = "permissions:admin.servers.nodes.create")]
|
||||||
public async Task<NodeDetailResponse> Create([FromBody] CreateNodeRequest request)
|
public async Task<ActionResult<NodeResponse>> CreateAsync([FromBody] CreateNodeRequest request)
|
||||||
{
|
{
|
||||||
var node = Mapper.Map<Node>(request);
|
var node = NodeMapper.ToNode(request);
|
||||||
|
|
||||||
node.TokenId = Formatter.GenerateString(6);
|
node.TokenId = Formatter.GenerateString(6);
|
||||||
node.Token = Formatter.GenerateString(32);
|
node.Token = Formatter.GenerateString(32);
|
||||||
|
|
||||||
var finalNode = await NodeRepository.Add(node);
|
var finalNode = await NodeRepository.AddAsync(node);
|
||||||
|
|
||||||
return CrudHelper.MapToResult(finalNode);
|
return NodeMapper.ToAdminNodeResponse(finalNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPatch("{id:int}")]
|
[HttpPatch("{id:int}")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.nodes.update")]
|
[Authorize(Policy = "permissions:admin.servers.nodes.update")]
|
||||||
public async Task<NodeDetailResponse> Update([FromRoute] int id, [FromBody] UpdateNodeRequest request)
|
public async Task<ActionResult<NodeResponse>> UpdateAsync([FromRoute] int id, [FromBody] UpdateNodeRequest request)
|
||||||
{
|
{
|
||||||
return await CrudHelper.Update(id, request);
|
var node = await NodeRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
return Problem("No node with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
NodeMapper.Merge(request, node);
|
||||||
|
await NodeRepository.UpdateAsync(node);
|
||||||
|
|
||||||
|
return NodeMapper.ToAdminNodeResponse(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{id:int}")]
|
[HttpDelete("{id:int}")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.nodes.delete")]
|
[Authorize(Policy = "permissions:admin.servers.nodes.delete")]
|
||||||
public async Task Delete([FromRoute] int id)
|
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||||
{
|
{
|
||||||
await CrudHelper.Delete(id);
|
var node = await NodeRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
return Problem("No node with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
await NodeRepository.RemoveAsync(node);
|
||||||
|
return Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonCore.Exceptions;
|
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Services;
|
using MoonlightServers.ApiServer.Services;
|
||||||
@@ -10,7 +10,7 @@ using MoonlightServers.Shared.Http.Responses.Admin.Nodes.Statistics;
|
|||||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
|
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/admin/servers/nodes")]
|
[Route("api/admin/servers/nodes/{nodeId:int}/statistics")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.nodes.statistics")]
|
[Authorize(Policy = "permissions:admin.servers.nodes.statistics")]
|
||||||
public class StatisticsController : Controller
|
public class StatisticsController : Controller
|
||||||
{
|
{
|
||||||
@@ -23,13 +23,19 @@ public class StatisticsController : Controller
|
|||||||
NodeRepository = nodeRepository;
|
NodeRepository = nodeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{nodeId:int}/statistics")]
|
[HttpGet]
|
||||||
public async Task<StatisticsResponse> Get([FromRoute] int nodeId)
|
[SuppressMessage("ReSharper.DPA", "DPA0011: High execution time of MVC action", MessageId = "time: 1142ms",
|
||||||
|
Justification = "The daemon has an artificial delay of one second to calculate accurate cpu usage values")]
|
||||||
|
public async Task<ActionResult<StatisticsResponse>> GetAsync([FromRoute] int nodeId)
|
||||||
{
|
{
|
||||||
var node = await GetNode(nodeId);
|
var node = await GetNodeAsync(nodeId);
|
||||||
var statistics = await NodeService.GetStatistics(node);
|
|
||||||
|
|
||||||
return new()
|
if (node.Value == null)
|
||||||
|
return node.Result ?? Problem("Unable to retrieve node");
|
||||||
|
|
||||||
|
var statistics = await NodeService.GetStatisticsAsync(node.Value);
|
||||||
|
|
||||||
|
return new StatisticsResponse()
|
||||||
{
|
{
|
||||||
Cpu = new()
|
Cpu = new()
|
||||||
{
|
{
|
||||||
@@ -58,13 +64,17 @@ public class StatisticsController : Controller
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{nodeId:int}/statistics/docker")]
|
[HttpGet("docker")]
|
||||||
public async Task<DockerStatisticsResponse> GetDocker([FromRoute] int nodeId)
|
public async Task<ActionResult<DockerStatisticsResponse>> GetDockerAsync([FromRoute] int nodeId)
|
||||||
{
|
{
|
||||||
var node = await GetNode(nodeId);
|
var node = await GetNodeAsync(nodeId);
|
||||||
var statistics = await NodeService.GetDockerStatistics(node);
|
|
||||||
|
|
||||||
return new()
|
if (node.Value == null)
|
||||||
|
return node.Result ?? Problem("Unable to retrieve node");
|
||||||
|
|
||||||
|
var statistics = await NodeService.GetDockerStatisticsAsync(node.Value);
|
||||||
|
|
||||||
|
return new DockerStatisticsResponse()
|
||||||
{
|
{
|
||||||
BuildCacheReclaimable = statistics.BuildCacheReclaimable,
|
BuildCacheReclaimable = statistics.BuildCacheReclaimable,
|
||||||
BuildCacheUsed = statistics.BuildCacheUsed,
|
BuildCacheUsed = statistics.BuildCacheUsed,
|
||||||
@@ -75,15 +85,15 @@ public class StatisticsController : Controller
|
|||||||
Version = statistics.Version
|
Version = statistics.Version
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Node> GetNode(int nodeId)
|
private async Task<ActionResult<Node>> GetNodeAsync(int nodeId)
|
||||||
{
|
{
|
||||||
var result = await NodeRepository
|
var result = await NodeRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
||||||
|
|
||||||
if (result == null)
|
if (result == null)
|
||||||
throw new HttpApiException("A node with this id could not be found", 404);
|
return Problem("A node with this id could not be found", statusCode: 404);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using MoonCore.Exceptions;
|
using MoonCore.Common;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Helpers;
|
|
||||||
using MoonCore.Models;
|
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.ApiServer.Mappers;
|
||||||
using MoonlightServers.Shared.Http.Responses.Admin.ServerVariables;
|
using MoonlightServers.Shared.Http.Responses.Admin.ServerVariables;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Servers;
|
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Servers;
|
||||||
@@ -17,32 +16,51 @@ public class ServerVariablesController : Controller
|
|||||||
private readonly DatabaseRepository<ServerVariable> VariableRepository;
|
private readonly DatabaseRepository<ServerVariable> VariableRepository;
|
||||||
private readonly DatabaseRepository<Server> ServerRepository;
|
private readonly DatabaseRepository<Server> ServerRepository;
|
||||||
|
|
||||||
public ServerVariablesController(DatabaseRepository<ServerVariable> variableRepository, DatabaseRepository<Server> serverRepository)
|
public ServerVariablesController(
|
||||||
|
DatabaseRepository<ServerVariable> variableRepository,
|
||||||
|
DatabaseRepository<Server> serverRepository
|
||||||
|
)
|
||||||
{
|
{
|
||||||
VariableRepository = variableRepository;
|
VariableRepository = variableRepository;
|
||||||
ServerRepository = serverRepository;
|
ServerRepository = serverRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId}/variables")]
|
[HttpGet("{serverId:int}/variables")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.get")]
|
[Authorize(Policy = "permissions:admin.servers.read")]
|
||||||
public async Task<PagedData<ServerVariableDetailResponse>> Get([FromRoute] int serverId, [FromQuery] int page, [FromQuery] int pageSize)
|
public async Task<ActionResult<CountedData<ServerVariableResponse>>> GetAsync(
|
||||||
|
[FromRoute] int serverId,
|
||||||
|
[FromQuery] int startIndex,
|
||||||
|
[FromQuery] int count
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var server = await ServerRepository
|
if (count > 100)
|
||||||
.Get()
|
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
|
||||||
|
|
||||||
if (server == null)
|
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
|
||||||
|
|
||||||
var variables = await VariableRepository
|
var serverExists = await ServerRepository
|
||||||
.Get()
|
.Get()
|
||||||
.Where(x => x.Server.Id == server.Id)
|
.AnyAsync(x => x.Id == serverId);
|
||||||
|
|
||||||
|
if (!serverExists)
|
||||||
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
var query = VariableRepository
|
||||||
|
.Get()
|
||||||
|
.Where(x => x.Server.Id == serverId);
|
||||||
|
|
||||||
|
var totalCount = await query.CountAsync();
|
||||||
|
|
||||||
|
var variables = await query
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.Skip(startIndex)
|
||||||
|
.Take(count)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ProjectToAdminResponse()
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
|
|
||||||
var castedVariables = variables
|
return new CountedData<ServerVariableResponse>()
|
||||||
.Select(x => Mapper.Map<ServerVariableDetailResponse>(x))
|
{
|
||||||
.ToArray();
|
Items = variables,
|
||||||
|
TotalCount = totalCount
|
||||||
return PagedData<ServerVariableDetailResponse>.Create(castedVariables, page, pageSize);
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using MoonCore.Exceptions;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MoonCore.Common;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Extended.Helpers;
|
|
||||||
using MoonCore.Helpers;
|
|
||||||
using MoonCore.Models;
|
|
||||||
using Moonlight.ApiServer.Database.Entities;
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.ApiServer.Mappers;
|
||||||
using MoonlightServers.ApiServer.Services;
|
using MoonlightServers.ApiServer.Services;
|
||||||
using MoonlightServers.Shared.Http.Requests.Admin.Servers;
|
using MoonlightServers.Shared.Http.Requests.Admin.Servers;
|
||||||
using MoonlightServers.Shared.Http.Responses.Admin.Servers;
|
using MoonlightServers.Shared.Http.Responses.Admin.Servers;
|
||||||
@@ -18,7 +17,6 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Servers;
|
|||||||
[Route("api/admin/servers")]
|
[Route("api/admin/servers")]
|
||||||
public class ServersController : Controller
|
public class ServersController : Controller
|
||||||
{
|
{
|
||||||
private readonly CrudHelper<Server, ServerDetailResponse> CrudHelper;
|
|
||||||
private readonly DatabaseRepository<Star> StarRepository;
|
private readonly DatabaseRepository<Star> StarRepository;
|
||||||
private readonly DatabaseRepository<Node> NodeRepository;
|
private readonly DatabaseRepository<Node> NodeRepository;
|
||||||
private readonly DatabaseRepository<Allocation> AllocationRepository;
|
private readonly DatabaseRepository<Allocation> AllocationRepository;
|
||||||
@@ -29,7 +27,6 @@ public class ServersController : Controller
|
|||||||
private readonly ServerService ServerService;
|
private readonly ServerService ServerService;
|
||||||
|
|
||||||
public ServersController(
|
public ServersController(
|
||||||
CrudHelper<Server, ServerDetailResponse> crudHelper,
|
|
||||||
DatabaseRepository<Star> starRepository,
|
DatabaseRepository<Star> starRepository,
|
||||||
DatabaseRepository<Node> nodeRepository,
|
DatabaseRepository<Node> nodeRepository,
|
||||||
DatabaseRepository<Allocation> allocationRepository,
|
DatabaseRepository<Allocation> allocationRepository,
|
||||||
@@ -40,7 +37,6 @@ public class ServersController : Controller
|
|||||||
ServerService serverService
|
ServerService serverService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
CrudHelper = crudHelper;
|
|
||||||
StarRepository = starRepository;
|
StarRepository = starRepository;
|
||||||
NodeRepository = nodeRepository;
|
NodeRepository = nodeRepository;
|
||||||
AllocationRepository = allocationRepository;
|
AllocationRepository = allocationRepository;
|
||||||
@@ -49,48 +45,70 @@ public class ServersController : Controller
|
|||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
ServerService = serverService;
|
ServerService = serverService;
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
|
|
||||||
CrudHelper.QueryModifier = servers => servers
|
|
||||||
.Include(x => x.Node)
|
|
||||||
.Include(x => x.Allocations)
|
|
||||||
.Include(x => x.Variables)
|
|
||||||
.Include(x => x.Star);
|
|
||||||
|
|
||||||
CrudHelper.LateMapper = (server, response) =>
|
|
||||||
{
|
|
||||||
response.NodeId = server.Node.Id;
|
|
||||||
response.StarId = server.Star.Id;
|
|
||||||
response.AllocationIds = server.Allocations.Select(x => x.Id).ToArray();
|
|
||||||
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Authorize(Policy = "permissions:admin.servers.get")]
|
[Authorize(Policy = "permissions:admin.servers.read")]
|
||||||
public async Task<IPagedData<ServerDetailResponse>> Get([FromQuery] int page, [FromQuery] int pageSize)
|
public async Task<ActionResult<CountedData<ServerResponse>>> GetAsync(
|
||||||
|
[FromQuery] int startIndex,
|
||||||
|
[FromQuery] int count
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return await CrudHelper.Get(page, pageSize);
|
if (count > 100)
|
||||||
|
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||||
|
|
||||||
|
var totalCount = await ServerRepository.Get().CountAsync();
|
||||||
|
|
||||||
|
var servers = await ServerRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Node)
|
||||||
|
.Include(x => x.Allocations)
|
||||||
|
.Include(x => x.Variables)
|
||||||
|
.Include(x => x.Star)
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.Skip(startIndex)
|
||||||
|
.Take(count)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ProjectToAdminResponse()
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
return new CountedData<ServerResponse>()
|
||||||
|
{
|
||||||
|
Items = servers,
|
||||||
|
TotalCount = totalCount
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id:int}")]
|
[HttpGet("{id:int}")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.get")]
|
[Authorize(Policy = "permissions:admin.servers.read")]
|
||||||
public async Task<ServerDetailResponse> GetSingle([FromRoute] int id)
|
public async Task<ActionResult<ServerResponse>> GetSingleAsync([FromRoute] int id)
|
||||||
{
|
{
|
||||||
return await CrudHelper.GetSingle(id);
|
var server = await ServerRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Node)
|
||||||
|
.Include(x => x.Allocations)
|
||||||
|
.Include(x => x.Variables)
|
||||||
|
.Include(x => x.Star)
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(x => x.Id == id)
|
||||||
|
.ProjectToAdminResponse()
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with that id found", statusCode: 404);
|
||||||
|
|
||||||
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize(Policy = "permissions:admin.servers.create")]
|
[Authorize(Policy = "permissions:admin.servers.write")]
|
||||||
public async Task<ServerDetailResponse> Create([FromBody] CreateServerRequest request)
|
public async Task<ActionResult<ServerResponse>> CreateAsync([FromBody] CreateServerRequest request)
|
||||||
{
|
{
|
||||||
// Construct model
|
|
||||||
var server = Mapper.Map<Server>(request);
|
|
||||||
|
|
||||||
// Check if owner user exist
|
// Check if owner user exist
|
||||||
if (UserRepository.Get().All(x => x.Id != request.OwnerId))
|
if (UserRepository.Get().All(x => x.Id != request.OwnerId))
|
||||||
throw new HttpApiException("No user with this id found", 400);
|
return Problem("No user with this id found", statusCode: 400);
|
||||||
|
|
||||||
|
// Check if the star exists
|
||||||
var star = await StarRepository
|
var star = await StarRepository
|
||||||
.Get()
|
.Get()
|
||||||
.Include(x => x.Variables)
|
.Include(x => x.Variables)
|
||||||
@@ -98,14 +116,14 @@ public class ServersController : Controller
|
|||||||
.FirstOrDefaultAsync(x => x.Id == request.StarId);
|
.FirstOrDefaultAsync(x => x.Id == request.StarId);
|
||||||
|
|
||||||
if (star == null)
|
if (star == null)
|
||||||
throw new HttpApiException("No star with this id found", 400);
|
return Problem("No star with this id found", statusCode: 400);
|
||||||
|
|
||||||
var node = await NodeRepository
|
var node = await NodeRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefaultAsync(x => x.Id == request.NodeId);
|
.FirstOrDefaultAsync(x => x.Id == request.NodeId);
|
||||||
|
|
||||||
if (node == null)
|
if (node == null)
|
||||||
throw new HttpApiException("No node with this id found", 400);
|
return Problem("No node with this id found", statusCode: 400);
|
||||||
|
|
||||||
var allocations = new List<Allocation>();
|
var allocations = new List<Allocation>();
|
||||||
|
|
||||||
@@ -140,13 +158,15 @@ public class ServersController : Controller
|
|||||||
|
|
||||||
if (allocations.Count < star.RequiredAllocations)
|
if (allocations.Count < star.RequiredAllocations)
|
||||||
{
|
{
|
||||||
throw new HttpApiException(
|
return Problem(
|
||||||
$"Unable to find enough free allocations. Found: {allocations.Count}, Required: {star.RequiredAllocations}",
|
$"Unable to find enough free allocations. Found: {allocations.Count}, Required: {star.RequiredAllocations}",
|
||||||
400
|
statusCode: 400
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var server = ServerMapper.ToServer(request);
|
||||||
|
|
||||||
// Set allocations
|
// Set allocations
|
||||||
server.Allocations = allocations;
|
server.Allocations = allocations;
|
||||||
|
|
||||||
@@ -170,34 +190,47 @@ public class ServersController : Controller
|
|||||||
server.Node = node;
|
server.Node = node;
|
||||||
server.Star = star;
|
server.Star = star;
|
||||||
|
|
||||||
var finalServer = await ServerRepository.Add(server);
|
var finalServer = await ServerRepository.AddAsync(server);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ServerService.Sync(finalServer);
|
await ServerService.SyncAsync(finalServer);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogError("Unable to sync server to node the server is assigned to: {e}", e);
|
Logger.LogError("Unable to sync server to node the server is assigned to: {e}", e);
|
||||||
|
|
||||||
// We are deleting the server from the database after the creation has failed
|
// We are deleting the server from the database after the creation has failed
|
||||||
// to ensure we wont have a bugged server in the database which doesnt exist on the node
|
// to ensure we won't have a bugged server in the database which doesn't exist on the node
|
||||||
await ServerRepository.Remove(finalServer);
|
await ServerRepository.RemoveAsync(finalServer);
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CrudHelper.MapToResult(finalServer);
|
return ServerMapper.ToAdminServerResponse(finalServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPatch("{id:int}")]
|
[HttpPatch("{id:int}")]
|
||||||
public async Task<ServerDetailResponse> Update([FromRoute] int id, [FromBody] UpdateServerRequest request)
|
[Authorize(Policy = "permissions:admin.servers.write")]
|
||||||
|
public async Task<ActionResult<ServerResponse>> UpdateAsync(
|
||||||
|
[FromRoute] int id,
|
||||||
|
[FromBody] UpdateServerRequest request
|
||||||
|
)
|
||||||
{
|
{
|
||||||
//TODO: Handle shrinking virtual disk
|
//TODO: Handle shrinking virtual disk
|
||||||
|
|
||||||
var server = await CrudHelper.GetSingleModel(id);
|
|
||||||
|
|
||||||
server = Mapper.Map(server, request);
|
var server = await ServerRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Node)
|
||||||
|
.Include(x => x.Allocations)
|
||||||
|
.Include(x => x.Variables)
|
||||||
|
.Include(x => x.Star)
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with that id found", statusCode: 404);
|
||||||
|
|
||||||
|
ServerMapper.Merge(request, server);
|
||||||
|
|
||||||
var allocations = new List<Allocation>();
|
var allocations = new List<Allocation>();
|
||||||
|
|
||||||
@@ -222,9 +255,9 @@ public class ServersController : Controller
|
|||||||
// Check if the specified allocations are enough for the star
|
// Check if the specified allocations are enough for the star
|
||||||
if (allocations.Count < server.Star.RequiredAllocations)
|
if (allocations.Count < server.Star.RequiredAllocations)
|
||||||
{
|
{
|
||||||
throw new HttpApiException(
|
return Problem(
|
||||||
$"You need to specify at least {server.Star.RequiredAllocations} allocation(s)",
|
$"You need to specify at least {server.Star.RequiredAllocations} allocation(s)",
|
||||||
400
|
statusCode: 400
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,24 +278,38 @@ public class ServersController : Controller
|
|||||||
serverVar.Value = variable.Value;
|
serverVar.Value = variable.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ServerRepository.Update(server);
|
await ServerRepository.UpdateAsync(server);
|
||||||
|
|
||||||
// Notify the node about the changes
|
// Notify the node about the changes
|
||||||
await ServerService.Sync(server);
|
await ServerService.SyncAsync(server);
|
||||||
|
|
||||||
return CrudHelper.MapToResult(server);
|
return ServerMapper.ToAdminServerResponse(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{id:int}")]
|
[HttpDelete("{id:int}")]
|
||||||
public async Task Delete([FromRoute] int id, [FromQuery] bool force = false)
|
public async Task<ActionResult> DeleteAsync([FromRoute] int id, [FromQuery] bool force = false)
|
||||||
{
|
{
|
||||||
var server = await CrudHelper.GetSingleModel(id);
|
var server = await ServerRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Node)
|
||||||
|
.Include(x => x.Star)
|
||||||
|
.Include(x => x.Variables)
|
||||||
|
.Include(x => x.Backups)
|
||||||
|
.Include(x => x.Allocations)
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with that id found", statusCode: 404);
|
||||||
|
|
||||||
|
server.Variables.Clear();
|
||||||
|
server.Backups.Clear();
|
||||||
|
server.Allocations.Clear();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// If the sync fails on the node and we aren't forcing the deletion,
|
// If the sync fails on the node and we aren't forcing the deletion,
|
||||||
// we don't want to delete it from the database yet
|
// we don't want to delete it from the database yet
|
||||||
await ServerService.SyncDelete(server);
|
await ServerService.SyncDeleteAsync(server);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -277,6 +324,7 @@ public class ServersController : Controller
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
await CrudHelper.Delete(id);
|
await ServerRepository.RemoveAsync(server);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,101 +1,159 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonCore.Exceptions;
|
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Extended.Helpers;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using MoonCore.Helpers;
|
using MoonCore.Common;
|
||||||
using MoonCore.Models;
|
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.ApiServer.Mappers;
|
||||||
using MoonlightServers.Shared.Http.Requests.Admin.StarDockerImages;
|
using MoonlightServers.Shared.Http.Requests.Admin.StarDockerImages;
|
||||||
using MoonlightServers.Shared.Http.Responses.Admin.StarDockerImages;
|
using MoonlightServers.Shared.Http.Responses.Admin.StarDockerImages;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/admin/servers/stars")]
|
[Route("api/admin/servers/stars/{starId:int}/dockerImages")]
|
||||||
public class StarDockerImagesController : Controller
|
public class StarDockerImagesController : Controller
|
||||||
{
|
{
|
||||||
private readonly CrudHelper<StarDockerImage, StarDockerImageDetailResponse> CrudHelper;
|
|
||||||
private readonly DatabaseRepository<Star> StarRepository;
|
private readonly DatabaseRepository<Star> StarRepository;
|
||||||
private readonly DatabaseRepository<StarDockerImage> StarDockerImageRepository;
|
private readonly DatabaseRepository<StarDockerImage> DockerImageRepository;
|
||||||
|
|
||||||
private Star Star;
|
|
||||||
|
|
||||||
public StarDockerImagesController(
|
public StarDockerImagesController(
|
||||||
CrudHelper<StarDockerImage, StarDockerImageDetailResponse> crudHelper,
|
|
||||||
DatabaseRepository<Star> starRepository,
|
DatabaseRepository<Star> starRepository,
|
||||||
DatabaseRepository<StarDockerImage> starDockerImageRepository
|
DatabaseRepository<StarDockerImage> dockerImageRepository
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
CrudHelper = crudHelper;
|
|
||||||
StarRepository = starRepository;
|
StarRepository = starRepository;
|
||||||
StarDockerImageRepository = starDockerImageRepository;
|
DockerImageRepository = dockerImageRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ApplyStar(int id)
|
[HttpGet]
|
||||||
|
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
||||||
|
public async Task<ActionResult<CountedData<StarDockerImageResponse>>> GetAsync(
|
||||||
|
[FromRoute] int starId,
|
||||||
|
[FromQuery] int startIndex,
|
||||||
|
[FromQuery] int count
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var starExists = StarRepository
|
||||||
|
.Get()
|
||||||
|
.Any(x => x.Id == starId);
|
||||||
|
|
||||||
|
if (!starExists)
|
||||||
|
return Problem("No star with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
var query = DockerImageRepository
|
||||||
|
.Get()
|
||||||
|
.Where(x => x.Star.Id == starId);
|
||||||
|
|
||||||
|
var totalCount = await query.CountAsync();
|
||||||
|
|
||||||
|
var dockerImages = await query
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.Skip(startIndex)
|
||||||
|
.Take(count)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ProjectToAdminResponse()
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
return new CountedData<StarDockerImageResponse>()
|
||||||
|
{
|
||||||
|
Items = dockerImages,
|
||||||
|
TotalCount = totalCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id:int}")]
|
||||||
|
[Authorize(Policy = "permissions:admin.servers.stars.read")]
|
||||||
|
public async Task<ActionResult<StarDockerImageResponse>> GetSingleAsync([FromRoute] int starId, [FromRoute] int id)
|
||||||
|
{
|
||||||
|
var starExists = StarRepository
|
||||||
|
.Get()
|
||||||
|
.Any(x => x.Id == starId);
|
||||||
|
|
||||||
|
if (!starExists)
|
||||||
|
return Problem("No star with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
var dockerImage = await DockerImageRepository
|
||||||
|
.Get()
|
||||||
|
.Where(x => x.Id == id && x.Star.Id == starId)
|
||||||
|
.ProjectToAdminResponse()
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (dockerImage == null)
|
||||||
|
return Problem("No star docker image with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
return dockerImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Authorize(Policy = "permissions:admin.servers.stars.write")]
|
||||||
|
public async Task<ActionResult<StarDockerImageResponse>> CreateAsync(
|
||||||
|
[FromRoute] int starId,
|
||||||
|
[FromBody] CreateStarDockerImageRequest request
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var star = await StarRepository
|
var star = await StarRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefaultAsync(x => x.Id == id);
|
.FirstOrDefaultAsync(x => x.Id == starId);
|
||||||
|
|
||||||
if (star == null)
|
if (star == null)
|
||||||
throw new HttpApiException("A star with this id could not be found", 404);
|
return Problem("No star with this id found", statusCode: 404);
|
||||||
|
|
||||||
Star = star;
|
var dockerImage = DockerImageMapper.ToDockerImage(request);
|
||||||
|
dockerImage.Star = star;
|
||||||
|
|
||||||
CrudHelper.QueryModifier = dockerImages =>
|
var finalDockerImage = await DockerImageRepository.AddAsync(dockerImage);
|
||||||
dockerImages.Where(x => x.Star.Id == star.Id);
|
|
||||||
|
return DockerImageMapper.ToAdminResponse(finalDockerImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{starId:int}/dockerImages")]
|
[HttpPatch("{id:int}")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
[Authorize(Policy = "permissions:admin.servers.stars.write")]
|
||||||
public async Task<IPagedData<StarDockerImageDetailResponse>> Get([FromRoute] int starId, [FromQuery] int page, [FromQuery] int pageSize)
|
public async Task<ActionResult<StarDockerImageResponse>> UpdateAsync(
|
||||||
|
[FromRoute] int starId,
|
||||||
|
[FromRoute] int id,
|
||||||
|
[FromBody] UpdateStarDockerImageRequest request
|
||||||
|
)
|
||||||
{
|
{
|
||||||
await ApplyStar(starId);
|
var starExists = StarRepository
|
||||||
|
.Get()
|
||||||
return await CrudHelper.Get(page, pageSize);
|
.Any(x => x.Id == starId);
|
||||||
|
|
||||||
|
if (!starExists)
|
||||||
|
return Problem("No star with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
var dockerImage = await DockerImageRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||||
|
|
||||||
|
if (dockerImage == null)
|
||||||
|
return Problem("No star docker image with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
DockerImageMapper.Merge(request, dockerImage);
|
||||||
|
await DockerImageRepository.UpdateAsync(dockerImage);
|
||||||
|
|
||||||
|
return DockerImageMapper.ToAdminResponse(dockerImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{starId:int}/dockerImages/{id:int}")]
|
[HttpDelete("{id:int}")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
[Authorize(Policy = "permissions:admin.servers.stars.write")]
|
||||||
public async Task<StarDockerImageDetailResponse> GetSingle([FromRoute] int starId, [FromRoute] int id)
|
public async Task<ActionResult> DeleteAsync([FromRoute] int starId, [FromRoute] int id)
|
||||||
{
|
{
|
||||||
await ApplyStar(starId);
|
var starExists = StarRepository
|
||||||
|
.Get()
|
||||||
|
.Any(x => x.Id == starId);
|
||||||
|
|
||||||
|
if (!starExists)
|
||||||
|
return Problem("No star with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
var dockerImage = await DockerImageRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||||
|
|
||||||
|
if (dockerImage == null)
|
||||||
|
return Problem("No star docker image with this id found", statusCode: 404);
|
||||||
|
|
||||||
return await CrudHelper.GetSingle(id);
|
await DockerImageRepository.RemoveAsync(dockerImage);
|
||||||
}
|
return NoContent();
|
||||||
|
|
||||||
[HttpPost("{starId:int}/dockerImages")]
|
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
|
||||||
public async Task<StarDockerImageDetailResponse> Create([FromRoute] int starId, [FromBody] CreateStarDockerImageRequest request)
|
|
||||||
{
|
|
||||||
await ApplyStar(starId);
|
|
||||||
|
|
||||||
var starDockerImage = Mapper.Map<StarDockerImage>(request);
|
|
||||||
starDockerImage.Star = Star;
|
|
||||||
|
|
||||||
var finalVariable = await StarDockerImageRepository.Add(starDockerImage);
|
|
||||||
|
|
||||||
return CrudHelper.MapToResult(finalVariable);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPatch("{starId:int}/dockerImages/{id:int}")]
|
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.update")]
|
|
||||||
public async Task<StarDockerImageDetailResponse> Update([FromRoute] int starId, [FromRoute] int id,
|
|
||||||
[FromBody] UpdateStarDockerImageRequest request)
|
|
||||||
{
|
|
||||||
await ApplyStar(starId);
|
|
||||||
|
|
||||||
return await CrudHelper.Update(id, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("{starId:int}/dockerImages/{id:int}")]
|
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.delete")]
|
|
||||||
public async Task Delete([FromRoute] int starId, [FromRoute] int id)
|
|
||||||
{
|
|
||||||
await ApplyStar(starId);
|
|
||||||
|
|
||||||
await CrudHelper.Delete(id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ using System.Text;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using MoonCore.Exceptions;
|
using MoonCore.Exceptions;
|
||||||
using MoonCore.Helpers;
|
using MoonlightServers.ApiServer.Mappers;
|
||||||
using MoonlightServers.ApiServer.Services;
|
using MoonlightServers.ApiServer.Services;
|
||||||
using MoonlightServers.Shared.Http.Responses.Admin.Stars;
|
using MoonlightServers.Shared.Http.Responses.Admin.Stars;
|
||||||
|
|
||||||
@@ -21,18 +21,15 @@ public class StarImportExportController : Controller
|
|||||||
|
|
||||||
[HttpGet("{starId:int}/export")]
|
[HttpGet("{starId:int}/export")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
||||||
public async Task Export([FromRoute] int starId)
|
public async Task<ActionResult> ExportAsync([FromRoute] int starId)
|
||||||
{
|
{
|
||||||
var exportedStar = await ImportExportService.Export(starId);
|
var exportedStar = await ImportExportService.ExportAsync(starId);
|
||||||
|
return Content(exportedStar, "application/json");
|
||||||
Response.StatusCode = 200;
|
|
||||||
Response.ContentType = "application/json";
|
|
||||||
await Response.WriteAsync(exportedStar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("import")]
|
[HttpPost("import")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
||||||
public async Task<StarDetailResponse> Import()
|
public async Task<StarResponse> ImportAsync()
|
||||||
{
|
{
|
||||||
if (Request.Form.Files.Count == 0)
|
if (Request.Form.Files.Count == 0)
|
||||||
throw new HttpApiException("No file to import provided", 400);
|
throw new HttpApiException("No file to import provided", 400);
|
||||||
@@ -45,8 +42,8 @@ public class StarImportExportController : Controller
|
|||||||
using var sr = new StreamReader(stream, Encoding.UTF8);
|
using var sr = new StreamReader(stream, Encoding.UTF8);
|
||||||
var content = await sr.ReadToEndAsync();
|
var content = await sr.ReadToEndAsync();
|
||||||
|
|
||||||
var star = await ImportExportService.Import(content);
|
var star = await ImportExportService.ImportAsync(content);
|
||||||
|
|
||||||
return Mapper.Map<StarDetailResponse>(star);
|
return StarMapper.ToAdminResponse(star);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,106 +1,160 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using MoonCore.Common;
|
||||||
using MoonCore.Exceptions;
|
using MoonCore.Exceptions;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Extended.Helpers;
|
|
||||||
using MoonCore.Helpers;
|
|
||||||
using MoonCore.Models;
|
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.ApiServer.Mappers;
|
||||||
using MoonlightServers.Shared.Http.Requests.Admin.StarVariables;
|
using MoonlightServers.Shared.Http.Requests.Admin.StarVariables;
|
||||||
using MoonlightServers.Shared.Http.Responses.Admin.StarVariables;
|
using MoonlightServers.Shared.Http.Responses.Admin.StarVariables;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/admin/servers/stars")]
|
[Route("api/admin/servers/stars/{starId:int}/variables")]
|
||||||
public class StarVariablesController : Controller
|
public class StarVariablesController : Controller
|
||||||
{
|
{
|
||||||
private readonly CrudHelper<StarVariable, StarVariableDetailResponse> CrudHelper;
|
|
||||||
private readonly DatabaseRepository<Star> StarRepository;
|
private readonly DatabaseRepository<Star> StarRepository;
|
||||||
private readonly DatabaseRepository<StarVariable> StarVariableRepository;
|
private readonly DatabaseRepository<StarVariable> VariableRepository;
|
||||||
|
|
||||||
private Star Star;
|
|
||||||
|
|
||||||
public StarVariablesController(
|
public StarVariablesController(
|
||||||
CrudHelper<StarVariable, StarVariableDetailResponse> crudHelper,
|
|
||||||
DatabaseRepository<Star> starRepository,
|
DatabaseRepository<Star> starRepository,
|
||||||
DatabaseRepository<StarVariable> starVariableRepository)
|
DatabaseRepository<StarVariable> variableRepository)
|
||||||
{
|
{
|
||||||
CrudHelper = crudHelper;
|
|
||||||
StarRepository = starRepository;
|
StarRepository = starRepository;
|
||||||
StarVariableRepository = starVariableRepository;
|
VariableRepository = variableRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ApplyStar(int id)
|
[HttpGet]
|
||||||
|
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
||||||
|
public async Task<ActionResult<CountedData<StarVariableResponse>>> GetAsync(
|
||||||
|
[FromRoute] int starId,
|
||||||
|
[FromQuery] int startIndex,
|
||||||
|
[FromQuery] int count
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var star = await StarRepository
|
if (count > 100)
|
||||||
|
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||||
|
|
||||||
|
var starExists = StarRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefaultAsync(x => x.Id == id);
|
.Any(x => x.Id == starId);
|
||||||
|
|
||||||
|
if (!starExists)
|
||||||
|
return Problem("No star with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
var query = VariableRepository
|
||||||
|
.Get()
|
||||||
|
.Where(x => x.Star.Id == starId);
|
||||||
|
|
||||||
|
var totalCount = await query.CountAsync();
|
||||||
|
|
||||||
|
var variables = await query
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.Skip(startIndex)
|
||||||
|
.Take(count)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ProjectToAdminResponse()
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
return new CountedData<StarVariableResponse>()
|
||||||
|
{
|
||||||
|
Items = variables,
|
||||||
|
TotalCount = totalCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id:int}")]
|
||||||
|
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
||||||
|
public async Task<StarVariableResponse> GetSingleAsync(
|
||||||
|
[FromRoute] int starId,
|
||||||
|
[FromRoute] int id
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var starExists = StarRepository
|
||||||
|
.Get()
|
||||||
|
.Any(x => x.Id == starId);
|
||||||
|
|
||||||
|
if (!starExists)
|
||||||
|
throw new HttpApiException("No star with this id found", 404);
|
||||||
|
|
||||||
|
var starVariable = await VariableRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||||
|
|
||||||
|
if (starVariable == null)
|
||||||
|
throw new HttpApiException("No variable with this id found", 404);
|
||||||
|
|
||||||
|
return StarVariableMapper.ToAdminResponse(starVariable);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("")]
|
||||||
|
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
||||||
|
public async Task<StarVariableResponse> CreateAsync([FromRoute] int starId,
|
||||||
|
[FromBody] CreateStarVariableRequest request)
|
||||||
|
{
|
||||||
|
var star = StarRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefault(x => x.Id == starId);
|
||||||
|
|
||||||
if (star == null)
|
if (star == null)
|
||||||
throw new HttpApiException("A star with this id could not be found", 404);
|
throw new HttpApiException("No star with this id found", 404);
|
||||||
|
|
||||||
Star = star;
|
var starVariable = StarVariableMapper.ToStarVariable(request);
|
||||||
|
starVariable.Star = star;
|
||||||
|
|
||||||
CrudHelper.QueryModifier = variables =>
|
await VariableRepository.AddAsync(starVariable);
|
||||||
variables.Where(x => x.Star.Id == star.Id);
|
|
||||||
|
return StarVariableMapper.ToAdminResponse(starVariable);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{starId:int}/variables")]
|
[HttpPatch("{id:int}")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
|
||||||
public async Task<IPagedData<StarVariableDetailResponse>> Get([FromRoute] int starId, [FromQuery] int page, [FromQuery] int pageSize)
|
|
||||||
{
|
|
||||||
await ApplyStar(starId);
|
|
||||||
|
|
||||||
return await CrudHelper.Get(page, pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{starId:int}/variables/{id:int}")]
|
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
|
||||||
public async Task<StarVariableDetailResponse> GetSingle([FromRoute] int starId, [FromRoute] int id)
|
|
||||||
{
|
|
||||||
await ApplyStar(starId);
|
|
||||||
|
|
||||||
return await CrudHelper.GetSingle(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("{starId:int}/variables")]
|
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
|
||||||
public async Task<StarVariableDetailResponse> Create([FromRoute] int starId, [FromBody] CreateStarVariableRequest request)
|
|
||||||
{
|
|
||||||
await ApplyStar(starId);
|
|
||||||
|
|
||||||
var starVariable = Mapper.Map<StarVariable>(request);
|
|
||||||
starVariable.Star = Star;
|
|
||||||
|
|
||||||
var finalVariable = await StarVariableRepository.Add(starVariable);
|
|
||||||
|
|
||||||
return CrudHelper.MapToResult(finalVariable);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPatch("{starId:int}/variables/{id:int}")]
|
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.update")]
|
[Authorize(Policy = "permissions:admin.servers.stars.update")]
|
||||||
public async Task<StarVariableDetailResponse> Update([FromRoute] int starId, [FromRoute] int id,
|
public async Task<StarVariableResponse> UpdateAsync(
|
||||||
[FromBody] UpdateStarVariableRequest request)
|
[FromRoute] int starId,
|
||||||
|
[FromRoute] int id,
|
||||||
|
[FromBody] UpdateStarVariableRequest request
|
||||||
|
)
|
||||||
{
|
{
|
||||||
await ApplyStar(starId);
|
var starExists = StarRepository
|
||||||
|
.Get()
|
||||||
var variable = await CrudHelper.GetSingleModel(id);
|
.Any(x => x.Id == starId);
|
||||||
|
|
||||||
variable = Mapper.Map(variable, request);
|
if (!starExists)
|
||||||
|
throw new HttpApiException("No star with this id found", 404);
|
||||||
|
|
||||||
|
var starVariable = await VariableRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||||
|
|
||||||
|
if (starVariable == null)
|
||||||
|
throw new HttpApiException("No variable with this id found", 404);
|
||||||
|
|
||||||
await StarVariableRepository.Update(variable);
|
StarVariableMapper.Merge(request, starVariable);
|
||||||
|
await VariableRepository.UpdateAsync(starVariable);
|
||||||
|
|
||||||
return CrudHelper.MapToResult(variable);
|
return StarVariableMapper.ToAdminResponse(starVariable);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{starId:int}/variables/{id:int}")]
|
[HttpDelete("{id:int}")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.delete")]
|
[Authorize(Policy = "permissions:admin.servers.stars.delete")]
|
||||||
public async Task Delete([FromRoute] int starId, [FromRoute] int id)
|
public async Task DeleteAsync([FromRoute] int starId, [FromRoute] int id)
|
||||||
{
|
{
|
||||||
await ApplyStar(starId);
|
var starExists = StarRepository
|
||||||
|
.Get()
|
||||||
|
.Any(x => x.Id == starId);
|
||||||
|
|
||||||
|
if (!starExists)
|
||||||
|
throw new HttpApiException("No star with this id found", 404);
|
||||||
|
|
||||||
|
var starVariable = await VariableRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||||
|
|
||||||
|
if (starVariable == null)
|
||||||
|
throw new HttpApiException("No variable with this id found", 404);
|
||||||
|
|
||||||
await CrudHelper.Delete(id);
|
await VariableRepository.RemoveAsync(starVariable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using MoonCore.Common;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Extended.Helpers;
|
|
||||||
using MoonCore.Helpers;
|
|
||||||
using MoonCore.Models;
|
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.ApiServer.Mappers;
|
||||||
using MoonlightServers.Shared.Http.Requests.Admin.Stars;
|
using MoonlightServers.Shared.Http.Requests.Admin.Stars;
|
||||||
using MoonlightServers.Shared.Http.Responses.Admin.StarDockerImages;
|
|
||||||
using MoonlightServers.Shared.Http.Responses.Admin.Stars;
|
using MoonlightServers.Shared.Http.Responses.Admin.Stars;
|
||||||
using MoonlightServers.Shared.Http.Responses.Admin.StarVariables;
|
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
||||||
|
|
||||||
@@ -17,35 +14,63 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
|||||||
[Route("api/admin/servers/stars")]
|
[Route("api/admin/servers/stars")]
|
||||||
public class StarsController : Controller
|
public class StarsController : Controller
|
||||||
{
|
{
|
||||||
private readonly CrudHelper<Star, StarDetailResponse> CrudHelper;
|
|
||||||
private readonly DatabaseRepository<Star> StarRepository;
|
private readonly DatabaseRepository<Star> StarRepository;
|
||||||
|
|
||||||
public StarsController(CrudHelper<Star, StarDetailResponse> crudHelper, DatabaseRepository<Star> starRepository)
|
public StarsController(DatabaseRepository<Star> starRepository)
|
||||||
{
|
{
|
||||||
CrudHelper = crudHelper;
|
|
||||||
StarRepository = starRepository;
|
StarRepository = starRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
[Authorize(Policy = "permissions:admin.servers.stars.read")]
|
||||||
public async Task<IPagedData<StarDetailResponse>> Get([FromQuery] int page, [FromQuery] int pageSize)
|
public async Task<ActionResult<CountedData<StarResponse>>> GetAsync(
|
||||||
|
[FromQuery] int startIndex,
|
||||||
|
[FromQuery] int count
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return await CrudHelper.Get(page, pageSize);
|
if (count > 100)
|
||||||
|
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||||
|
|
||||||
|
var totalCount = await StarRepository.Get().CountAsync();
|
||||||
|
|
||||||
|
var stars = await StarRepository
|
||||||
|
.Get()
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.Skip(startIndex)
|
||||||
|
.Take(count)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ProjectToAdminResponse()
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
return new CountedData<StarResponse>()
|
||||||
|
{
|
||||||
|
Items = stars,
|
||||||
|
TotalCount = totalCount
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id:int}")]
|
[HttpGet("{id:int}")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
[Authorize(Policy = "permissions:admin.servers.stars.read")]
|
||||||
public async Task<StarDetailResponse> GetSingle([FromRoute] int id)
|
public async Task<ActionResult<StarResponse>> GetSingleAsync([FromRoute] int id)
|
||||||
{
|
{
|
||||||
return await CrudHelper.GetSingle(id);
|
var star = await StarRepository
|
||||||
|
.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.ProjectToAdminResponse()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (star == null)
|
||||||
|
return Problem("No star with that id found", statusCode: 404);
|
||||||
|
|
||||||
|
return star;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
||||||
public async Task<StarDetailResponse> Create([FromBody] CreateStarRequest request)
|
public async Task<ActionResult<StarResponse>> CreateAsync([FromBody] CreateStarRequest request)
|
||||||
{
|
{
|
||||||
var star = Mapper.Map<Star>(request);
|
var star = StarMapper.ToStar(request);
|
||||||
|
|
||||||
// Default values
|
// Default values
|
||||||
star.DonateUrl = null;
|
star.DonateUrl = null;
|
||||||
star.UpdateUrl = null;
|
star.UpdateUrl = null;
|
||||||
@@ -61,22 +86,43 @@ public class StarsController : Controller
|
|||||||
star.DefaultDockerImage = -1;
|
star.DefaultDockerImage = -1;
|
||||||
star.ParseConfiguration = "[]";
|
star.ParseConfiguration = "[]";
|
||||||
|
|
||||||
var finalStar = await StarRepository.Add(star);
|
var finalStar = await StarRepository.AddAsync(star);
|
||||||
|
|
||||||
return CrudHelper.MapToResult(finalStar);
|
return StarMapper.ToAdminResponse(finalStar);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPatch("{id:int}")]
|
[HttpPatch("{id:int}")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.update")]
|
[Authorize(Policy = "permissions:admin.servers.stars.update")]
|
||||||
public async Task<StarDetailResponse> Update([FromRoute] int id, [FromBody] UpdateStarRequest request)
|
public async Task<ActionResult<StarResponse>> UpdateAsync(
|
||||||
|
[FromRoute] int id,
|
||||||
|
[FromBody] UpdateStarRequest request
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return await CrudHelper.Update(id, request);
|
var star = await StarRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (star == null)
|
||||||
|
return Problem("No star with that id found", statusCode: 404);
|
||||||
|
|
||||||
|
StarMapper.Merge(request, star);
|
||||||
|
await StarRepository.UpdateAsync(star);
|
||||||
|
|
||||||
|
return StarMapper.ToAdminResponse(star);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{id:int}")]
|
[HttpDelete("{id:int}")]
|
||||||
[Authorize(Policy = "permissions:admin.servers.stars.delete")]
|
[Authorize(Policy = "permissions:admin.servers.stars.delete")]
|
||||||
public async Task Delete([FromRoute] int id)
|
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||||
{
|
{
|
||||||
await CrudHelper.Delete(id);
|
var star = await StarRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (star == null)
|
||||||
|
return Problem("No star with that id found", statusCode: 404);
|
||||||
|
|
||||||
|
await StarRepository.RemoveAsync(star);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonCore.Exceptions;
|
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using Moonlight.ApiServer.Database.Entities;
|
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Services;
|
using MoonlightServers.ApiServer.Services;
|
||||||
using MoonlightServers.DaemonShared.Enums;
|
using MoonlightServers.DaemonShared.Enums;
|
||||||
|
using MoonlightServers.Shared.Constants;
|
||||||
using MoonlightServers.Shared.Enums;
|
using MoonlightServers.Shared.Enums;
|
||||||
using MoonlightServers.Shared.Http.Requests.Client.Servers.Files;
|
using MoonlightServers.Shared.Http.Requests.Client.Servers.Files;
|
||||||
using MoonlightServers.Shared.Http.Responses.Client.Servers.Files;
|
using MoonlightServers.Shared.Http.Responses.Client.Servers.Files;
|
||||||
@@ -15,7 +14,7 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
|||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/client/servers")]
|
[Route("api/client/servers/{serverId:int}/files")]
|
||||||
public class FilesController : Controller
|
public class FilesController : Controller
|
||||||
{
|
{
|
||||||
private readonly DatabaseRepository<Server> ServerRepository;
|
private readonly DatabaseRepository<Server> ServerRepository;
|
||||||
@@ -36,51 +35,83 @@ public class FilesController : Controller
|
|||||||
AuthorizeService = authorizeService;
|
AuthorizeService = authorizeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/files/list")]
|
[HttpGet("list")]
|
||||||
public async Task<ServerFilesEntryResponse[]> List([FromRoute] int serverId, [FromQuery] string path)
|
public async Task<ActionResult<ServerFilesEntryResponse[]>> ListAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId, ServerPermissionType.Read);
|
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.Read);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
var entries = await ServerFileSystemService.List(server, path);
|
var entries = await ServerFileSystemService.ListAsync(server.Value, path);
|
||||||
|
|
||||||
return entries.Select(x => new ServerFilesEntryResponse()
|
return entries.Select(x => new ServerFilesEntryResponse()
|
||||||
{
|
{
|
||||||
Name = x.Name,
|
Name = x.Name,
|
||||||
Size = x.Size,
|
Size = x.Size,
|
||||||
IsFile = x.IsFile,
|
IsFolder = x.IsFolder,
|
||||||
CreatedAt = x.CreatedAt,
|
CreatedAt = x.CreatedAt,
|
||||||
UpdatedAt = x.UpdatedAt
|
UpdatedAt = x.UpdatedAt
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/files/move")]
|
[HttpPost("move")]
|
||||||
public async Task Move([FromRoute] int serverId, [FromQuery] string oldPath, [FromQuery] string newPath)
|
public async Task<ActionResult> MoveAsync([FromRoute] int serverId, [FromQuery] string oldPath, [FromQuery] string newPath)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
|
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
await ServerFileSystemService.Move(server, oldPath, newPath);
|
await ServerFileSystemService.MoveAsync(server.Value, oldPath, newPath);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{serverId:int}/files/delete")]
|
[HttpDelete("delete")]
|
||||||
public async Task Delete([FromRoute] int serverId, [FromQuery] string path)
|
public async Task<ActionResult> DeleteAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
|
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
await ServerFileSystemService.Delete(server, path);
|
await ServerFileSystemService.DeleteAsync(server.Value, path);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/files/mkdir")]
|
[HttpPost("mkdir")]
|
||||||
public async Task Mkdir([FromRoute] int serverId, [FromQuery] string path)
|
public async Task<ActionResult> MkdirAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
|
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
await ServerFileSystemService.Mkdir(server, path);
|
await ServerFileSystemService.MkdirAsync(server.Value, path);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/files/upload")]
|
[HttpPost("touch")]
|
||||||
public async Task<ServerFilesUploadResponse> Upload([FromRoute] int serverId)
|
public async Task<ActionResult> TouchAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
|
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
await ServerFileSystemService.MkdirAsync(server.Value, path);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("upload")]
|
||||||
|
public async Task<ActionResult<ServerFilesUploadResponse>> UploadAsync([FromRoute] int serverId)
|
||||||
|
{
|
||||||
|
var serverResult = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||||
|
|
||||||
|
if (serverResult.Value == null)
|
||||||
|
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
var server = serverResult.Value;
|
||||||
|
|
||||||
var accessToken = NodeService.CreateAccessToken(
|
var accessToken = NodeService.CreateAccessToken(
|
||||||
server.Node,
|
server.Node,
|
||||||
@@ -104,10 +135,15 @@ public class FilesController : Controller
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/files/download")]
|
[HttpGet("download")]
|
||||||
public async Task<ServerFilesDownloadResponse> Download([FromRoute] int serverId, [FromQuery] string path)
|
public async Task<ActionResult<ServerFilesDownloadResponse>> DownloadAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId, ServerPermissionType.Read);
|
var serverResult = await GetServerByIdAsync(serverId, ServerPermissionLevel.Read);
|
||||||
|
|
||||||
|
if (serverResult.Value == null)
|
||||||
|
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
var server = serverResult.Value;
|
||||||
|
|
||||||
var accessToken = NodeService.CreateAccessToken(
|
var accessToken = NodeService.CreateAccessToken(
|
||||||
server.Node,
|
server.Node,
|
||||||
@@ -132,29 +168,37 @@ public class FilesController : Controller
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/files/compress")]
|
[HttpPost("compress")]
|
||||||
public async Task Compress([FromRoute] int serverId, [FromBody] ServerFilesCompressRequest request)
|
public async Task<ActionResult> CompressAsync([FromRoute] int serverId, [FromBody] ServerFilesCompressRequest request)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
|
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
if (!Enum.TryParse(request.Type, true, out CompressType type))
|
if (!Enum.TryParse(request.Type, true, out CompressType type))
|
||||||
throw new HttpApiException("Invalid compress type provided", 400);
|
return Problem("Invalid compress type provided", statusCode: 400);
|
||||||
|
|
||||||
await ServerFileSystemService.Compress(server, type, request.Items, request.Destination);
|
await ServerFileSystemService.CompressAsync(server.Value, type, request.Items, request.Destination);
|
||||||
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/files/decompress")]
|
[HttpPost("decompress")]
|
||||||
public async Task Decompress([FromRoute] int serverId, [FromBody] ServerFilesDecompressRequest request)
|
public async Task<ActionResult> DecompressAsync([FromRoute] int serverId, [FromBody] ServerFilesDecompressRequest request)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
|
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
if (!Enum.TryParse(request.Type, true, out CompressType type))
|
if (!Enum.TryParse(request.Type, true, out CompressType type))
|
||||||
throw new HttpApiException("Invalid compress type provided", 400);
|
return Problem("Invalid decompress type provided", statusCode: 400);
|
||||||
|
|
||||||
await ServerFileSystemService.Decompress(server, type, request.Path, request.Destination);
|
await ServerFileSystemService.DecompressAsync(server.Value, type, request.Path, request.Destination);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Server> GetServerById(int serverId, ServerPermissionType type)
|
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId, ServerPermissionLevel level)
|
||||||
{
|
{
|
||||||
var server = await ServerRepository
|
var server = await ServerRepository
|
||||||
.Get()
|
.Get()
|
||||||
@@ -162,18 +206,19 @@ public class FilesController : Controller
|
|||||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
var authorizeResult = await AuthorizeService.Authorize(
|
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||||
User, server,
|
User, server,
|
||||||
permission => permission.Name == "files" && permission.Type >= type
|
ServerPermissionConstants.Files,
|
||||||
|
level
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!authorizeResult.Succeeded)
|
if (!authorizeResult.Succeeded)
|
||||||
{
|
{
|
||||||
throw new HttpApiException(
|
return Problem(
|
||||||
authorizeResult.Message ?? "No permission for the requested resource",
|
authorizeResult.Message ?? "No permission for the requested resource",
|
||||||
403
|
statusCode: 403
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,64 +1,74 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonCore.Exceptions;
|
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Helpers;
|
|
||||||
using Moonlight.ApiServer.Database.Entities;
|
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Services;
|
using MoonlightServers.ApiServer.Services;
|
||||||
|
using MoonlightServers.Shared.Constants;
|
||||||
using MoonlightServers.Shared.Enums;
|
using MoonlightServers.Shared.Enums;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("api/client/servers")]
|
[Route("api/client/servers/{serverId:int}")]
|
||||||
public class PowerController : Controller
|
public class PowerController : Controller
|
||||||
{
|
{
|
||||||
private readonly DatabaseRepository<Server> ServerRepository;
|
private readonly DatabaseRepository<Server> ServerRepository;
|
||||||
private readonly DatabaseRepository<User> UserRepository;
|
|
||||||
private readonly ServerService ServerService;
|
private readonly ServerService ServerService;
|
||||||
private readonly ServerAuthorizeService AuthorizeService;
|
private readonly ServerAuthorizeService AuthorizeService;
|
||||||
|
|
||||||
public PowerController(
|
public PowerController(
|
||||||
DatabaseRepository<Server> serverRepository,
|
DatabaseRepository<Server> serverRepository,
|
||||||
DatabaseRepository<User> userRepository,
|
|
||||||
ServerService serverService,
|
ServerService serverService,
|
||||||
ServerAuthorizeService authorizeService
|
ServerAuthorizeService authorizeService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ServerRepository = serverRepository;
|
ServerRepository = serverRepository;
|
||||||
UserRepository = userRepository;
|
|
||||||
ServerService = serverService;
|
ServerService = serverService;
|
||||||
AuthorizeService = authorizeService;
|
AuthorizeService = authorizeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/start")]
|
[HttpPost("start")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task Start([FromRoute] int serverId)
|
public async Task<ActionResult> StartAsync([FromRoute] int serverId)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId);
|
var server = await GetServerByIdAsync(serverId);
|
||||||
await ServerService.Start(server);
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
await ServerService.StartAsync(server.Value);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/stop")]
|
[HttpPost("stop")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task Stop([FromRoute] int serverId)
|
public async Task<ActionResult> StopAsync([FromRoute] int serverId)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId);
|
var server = await GetServerByIdAsync(serverId);
|
||||||
await ServerService.Stop(server);
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
await ServerService.StopAsync(server.Value);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/kill")]
|
[HttpPost("kill")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task Kill([FromRoute] int serverId)
|
public async Task<ActionResult> KillAsync([FromRoute] int serverId)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId);
|
var server = await GetServerByIdAsync(serverId);
|
||||||
await ServerService.Kill(server);
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
await ServerService.KillAsync(server.Value);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Server> GetServerById(int serverId)
|
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId)
|
||||||
{
|
{
|
||||||
var server = await ServerRepository
|
var server = await ServerRepository
|
||||||
.Get()
|
.Get()
|
||||||
@@ -66,21 +76,22 @@ public class PowerController : Controller
|
|||||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
var authorizeResult = await AuthorizeService.Authorize(
|
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||||
User, server,
|
User, server,
|
||||||
permission => permission.Name == "power" && permission.Type >= ServerPermissionType.ReadWrite
|
ServerPermissionConstants.Power,
|
||||||
|
ServerPermissionLevel.ReadWrite
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!authorizeResult.Succeeded)
|
if (!authorizeResult.Succeeded)
|
||||||
{
|
{
|
||||||
throw new HttpApiException(
|
return Problem(
|
||||||
authorizeResult.Message ?? "No permission for the requested resource",
|
authorizeResult.Message ?? "No permission for the requested resource",
|
||||||
403
|
statusCode: 403
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@ using System.Security.Claims;
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonCore.Exceptions;
|
using MoonCore.Common;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Models;
|
|
||||||
using Moonlight.ApiServer.Database.Entities;
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Extensions;
|
using MoonlightServers.ApiServer.Extensions;
|
||||||
using MoonlightServers.ApiServer.Models;
|
using MoonlightServers.ApiServer.Mappers;
|
||||||
using MoonlightServers.ApiServer.Services;
|
using MoonlightServers.ApiServer.Services;
|
||||||
|
using MoonlightServers.Shared.Constants;
|
||||||
using MoonlightServers.Shared.Enums;
|
using MoonlightServers.Shared.Enums;
|
||||||
|
using MoonlightServers.Shared.Http.Requests.Client.Servers;
|
||||||
using MoonlightServers.Shared.Http.Responses.Client.Servers;
|
using MoonlightServers.Shared.Http.Responses.Client.Servers;
|
||||||
using MoonlightServers.Shared.Http.Responses.Client.Servers.Allocations;
|
using MoonlightServers.Shared.Http.Responses.Client.Servers.Allocations;
|
||||||
using MoonlightServers.Shared.Models;
|
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
||||||
|
|
||||||
@@ -47,12 +47,18 @@ public class ServersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<PagedData<ServerDetailResponse>> GetAll([FromQuery] int page, [FromQuery] int pageSize)
|
public async Task<ActionResult<CountedData<ServerDetailResponse>>> GetAllAsync(
|
||||||
|
[FromQuery] int startIndex,
|
||||||
|
[FromQuery] int count
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var userIdClaim = User.FindFirstValue("userId");
|
if (count > 100)
|
||||||
|
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||||
|
|
||||||
|
var userIdClaim = User.FindFirstValue("UserId");
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(userIdClaim))
|
if (string.IsNullOrEmpty(userIdClaim))
|
||||||
throw new HttpApiException("Only users are able to use this endpoint", 400);
|
return Problem("Only users are able to use this endpoint", statusCode: 400);
|
||||||
|
|
||||||
var userId = int.Parse(userIdClaim);
|
var userId = int.Parse(userIdClaim);
|
||||||
|
|
||||||
@@ -63,8 +69,14 @@ public class ServersController : Controller
|
|||||||
.Include(x => x.Node)
|
.Include(x => x.Node)
|
||||||
.Where(x => x.OwnerId == userId);
|
.Where(x => x.OwnerId == userId);
|
||||||
|
|
||||||
var count = await query.CountAsync();
|
var totalCount = await query.CountAsync();
|
||||||
var items = await query.Skip(page * pageSize).Take(pageSize).ToArrayAsync();
|
|
||||||
|
var items = await query
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.Skip(startIndex)
|
||||||
|
.Take(count)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
var mappedItems = items.Select(x => new ServerDetailResponse()
|
var mappedItems = items.Select(x => new ServerDetailResponse()
|
||||||
{
|
{
|
||||||
@@ -83,23 +95,26 @@ public class ServersController : Controller
|
|||||||
}).ToArray()
|
}).ToArray()
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
return new PagedData<ServerDetailResponse>()
|
return new CountedData<ServerDetailResponse>()
|
||||||
{
|
{
|
||||||
Items = mappedItems,
|
Items = mappedItems,
|
||||||
CurrentPage = page,
|
TotalCount = totalCount
|
||||||
PageSize = pageSize,
|
|
||||||
TotalItems = count,
|
|
||||||
TotalPages = count == 0 ? 0 : count / pageSize
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("shared")]
|
[HttpGet("shared")]
|
||||||
public async Task<PagedData<ServerDetailResponse>> GetAllShared([FromQuery] int page, [FromQuery] int pageSize)
|
public async Task<ActionResult<CountedData<ServerDetailResponse>>> GetAllSharedAsync(
|
||||||
|
[FromQuery] int startIndex,
|
||||||
|
[FromQuery] int count
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var userIdClaim = User.FindFirstValue("userId");
|
if (count > 100)
|
||||||
|
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||||
|
|
||||||
|
var userIdClaim = User.FindFirstValue("UserId");
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(userIdClaim))
|
if (string.IsNullOrEmpty(userIdClaim))
|
||||||
throw new HttpApiException("Only users are able to use this endpoint", 400);
|
return Problem("Only users are able to use this endpoint", statusCode: 400);
|
||||||
|
|
||||||
var userId = int.Parse(userIdClaim);
|
var userId = int.Parse(userIdClaim);
|
||||||
|
|
||||||
@@ -113,8 +128,13 @@ public class ServersController : Controller
|
|||||||
.ThenInclude(x => x.Allocations)
|
.ThenInclude(x => x.Allocations)
|
||||||
.Where(x => x.UserId == userId);
|
.Where(x => x.UserId == userId);
|
||||||
|
|
||||||
var count = await query.CountAsync();
|
var totalCount = await query.CountAsync();
|
||||||
var items = await query.Skip(page * pageSize).Take(pageSize).ToArrayAsync();
|
|
||||||
|
var items = await query
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.Skip(startIndex)
|
||||||
|
.Take(count)
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
var ownerIds = items
|
var ownerIds = items
|
||||||
.Select(x => x.Server.OwnerId)
|
.Select(x => x.Server.OwnerId)
|
||||||
@@ -144,22 +164,19 @@ public class ServersController : Controller
|
|||||||
Share = new()
|
Share = new()
|
||||||
{
|
{
|
||||||
SharedBy = owners.First(y => y.Id == x.Server.OwnerId).Username,
|
SharedBy = owners.First(y => y.Id == x.Server.OwnerId).Username,
|
||||||
Permissions = x.Content.Permissions.ToArray()
|
Permissions = ShareMapper.MapToPermissionLevels(x.Content)
|
||||||
}
|
}
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
return new PagedData<ServerDetailResponse>()
|
return new CountedData<ServerDetailResponse>()
|
||||||
{
|
{
|
||||||
Items = mappedItems,
|
Items = mappedItems,
|
||||||
CurrentPage = page,
|
TotalCount = totalCount
|
||||||
PageSize = pageSize,
|
|
||||||
TotalItems = count,
|
|
||||||
TotalPages = count == 0 ? 0 : count / pageSize
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}")]
|
[HttpGet("{serverId:int}")]
|
||||||
public async Task<ServerDetailResponse> Get([FromRoute] int serverId)
|
public async Task<ActionResult<ServerDetailResponse>> GetAsync([FromRoute] int serverId)
|
||||||
{
|
{
|
||||||
var server = await ServerRepository
|
var server = await ServerRepository
|
||||||
.Get()
|
.Get()
|
||||||
@@ -169,15 +186,20 @@ public class ServersController : Controller
|
|||||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
var authorizationResult = await AuthorizeService.Authorize(User, server);
|
var authorizationResult = await AuthorizeService.AuthorizeAsync(
|
||||||
|
User,
|
||||||
|
server,
|
||||||
|
String.Empty,
|
||||||
|
ServerPermissionLevel.None
|
||||||
|
);
|
||||||
|
|
||||||
if (!authorizationResult.Succeeded)
|
if (!authorizationResult.Succeeded)
|
||||||
{
|
{
|
||||||
throw new HttpApiException(
|
return Problem(
|
||||||
authorizationResult.Message ?? "No server with this id found",
|
authorizationResult.Message ?? "No server with this id found",
|
||||||
404
|
statusCode: 404
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +231,7 @@ public class ServersController : Controller
|
|||||||
response.Share = new()
|
response.Share = new()
|
||||||
{
|
{
|
||||||
SharedBy = owner.Username,
|
SharedBy = owner.Username,
|
||||||
Permissions = authorizationResult.Share.Content.Permissions.ToArray()
|
Permissions = ShareMapper.MapToPermissionLevels(authorizationResult.Share.Content)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,11 +239,18 @@ public class ServersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/status")]
|
[HttpGet("{serverId:int}/status")]
|
||||||
public async Task<ServerStatusResponse> GetStatus([FromRoute] int serverId)
|
public async Task<ActionResult<ServerStatusResponse>> GetStatusAsync([FromRoute] int serverId)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId);
|
var server = await GetServerByIdAsync(
|
||||||
|
serverId,
|
||||||
|
ServerPermissionConstants.Console,
|
||||||
|
ServerPermissionLevel.None
|
||||||
|
);
|
||||||
|
|
||||||
var status = await ServerService.GetStatus(server);
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
var status = await ServerService.GetStatusAsync(server.Value);
|
||||||
|
|
||||||
return new ServerStatusResponse()
|
return new ServerStatusResponse()
|
||||||
{
|
{
|
||||||
@@ -230,13 +259,19 @@ public class ServersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/ws")]
|
[HttpGet("{serverId:int}/ws")]
|
||||||
public async Task<ServerWebSocketResponse> GetWebSocket([FromRoute] int serverId)
|
public async Task<ActionResult<ServerWebSocketResponse>> GetWebSocketAsync([FromRoute] int serverId)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(
|
var serverResult = await GetServerByIdAsync(
|
||||||
serverId,
|
serverId,
|
||||||
permission => permission is { Name: "console", Type: >= ServerPermissionType.Read }
|
ServerPermissionConstants.Console,
|
||||||
|
ServerPermissionLevel.Read
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (serverResult.Value == null)
|
||||||
|
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
var server = serverResult.Value;
|
||||||
|
|
||||||
// TODO: Handle transparent node proxy
|
// TODO: Handle transparent node proxy
|
||||||
|
|
||||||
var accessToken = NodeService.CreateAccessToken(server.Node, parameters =>
|
var accessToken = NodeService.CreateAccessToken(server.Node, parameters =>
|
||||||
@@ -258,14 +293,18 @@ public class ServersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/logs")]
|
[HttpGet("{serverId:int}/logs")]
|
||||||
public async Task<ServerLogsResponse> GetLogs([FromRoute] int serverId)
|
public async Task<ActionResult<ServerLogsResponse>> GetLogsAsync([FromRoute] int serverId)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(
|
var server = await GetServerByIdAsync(
|
||||||
serverId,
|
serverId,
|
||||||
permission => permission is { Name: "console", Type: >= ServerPermissionType.Read }
|
ServerPermissionConstants.Console,
|
||||||
|
ServerPermissionLevel.Read
|
||||||
);
|
);
|
||||||
|
|
||||||
var logs = await ServerService.GetLogs(server);
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
var logs = await ServerService.GetLogsAsync(server.Value);
|
||||||
|
|
||||||
return new ServerLogsResponse()
|
return new ServerLogsResponse()
|
||||||
{
|
{
|
||||||
@@ -274,13 +313,18 @@ public class ServersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/stats")]
|
[HttpGet("{serverId:int}/stats")]
|
||||||
public async Task<ServerStatsResponse> GetStats([FromRoute] int serverId)
|
public async Task<ActionResult<ServerStatsResponse>> GetStatsAsync([FromRoute] int serverId)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(
|
var server = await GetServerByIdAsync(
|
||||||
serverId
|
serverId,
|
||||||
|
ServerPermissionConstants.Console,
|
||||||
|
ServerPermissionLevel.Read
|
||||||
);
|
);
|
||||||
|
|
||||||
var stats = await ServerService.GetStats(server);
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
var stats = await ServerService.GetStatsAsync(server.Value);
|
||||||
|
|
||||||
return new ServerStatsResponse()
|
return new ServerStatsResponse()
|
||||||
{
|
{
|
||||||
@@ -293,7 +337,25 @@ public class ServersController : Controller
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Server> GetServerById(int serverId, Func<ServerSharePermission, bool>? filter = null)
|
[HttpPost("{serverId:int}/command")]
|
||||||
|
public async Task<ActionResult> CommandAsync([FromRoute] int serverId, [FromBody] ServerCommandRequest request)
|
||||||
|
{
|
||||||
|
var server = await GetServerByIdAsync(
|
||||||
|
serverId,
|
||||||
|
ServerPermissionConstants.Console,
|
||||||
|
ServerPermissionLevel.ReadWrite
|
||||||
|
);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
await ServerService.RunCommandAsync(server.Value, request.Command);
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId, string permissionId,
|
||||||
|
ServerPermissionLevel level)
|
||||||
{
|
{
|
||||||
var server = await ServerRepository
|
var server = await ServerRepository
|
||||||
.Get()
|
.Get()
|
||||||
@@ -301,15 +363,15 @@ public class ServersController : Controller
|
|||||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
var authorizeResult = await AuthorizeService.Authorize(User, server, filter);
|
var authorizeResult = await AuthorizeService.AuthorizeAsync(User, server, permissionId, level);
|
||||||
|
|
||||||
if (!authorizeResult.Succeeded)
|
if (!authorizeResult.Succeeded)
|
||||||
{
|
{
|
||||||
throw new HttpApiException(
|
return Problem(
|
||||||
authorizeResult.Message ?? "No permission for the requested resource",
|
authorizeResult.Message ?? "No permission for the requested resource",
|
||||||
403
|
statusCode: 403
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonCore.Exceptions;
|
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Services;
|
using MoonlightServers.ApiServer.Services;
|
||||||
|
using MoonlightServers.Shared.Constants;
|
||||||
using MoonlightServers.Shared.Enums;
|
using MoonlightServers.Shared.Enums;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
||||||
@@ -31,13 +31,18 @@ public class SettingsController : Controller
|
|||||||
|
|
||||||
[HttpPost("{serverId:int}/install")]
|
[HttpPost("{serverId:int}/install")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task Install([FromRoute] int serverId)
|
public async Task<ActionResult> InstallAsync([FromRoute] int serverId)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId);
|
var server = await GetServerByIdAsync(serverId);
|
||||||
await ServerService.Install(server);
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
await ServerService.InstallAsync(server.Value);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Server> GetServerById(int serverId)
|
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId)
|
||||||
{
|
{
|
||||||
var server = await ServerRepository
|
var server = await ServerRepository
|
||||||
.Get()
|
.Get()
|
||||||
@@ -45,18 +50,19 @@ public class SettingsController : Controller
|
|||||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
var authorizeResult = await AuthorizeService.Authorize(
|
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||||
User, server,
|
User, server,
|
||||||
permission => permission is { Name: "settings", Type: >= ServerPermissionType.ReadWrite }
|
ServerPermissionConstants.Settings,
|
||||||
|
ServerPermissionLevel.ReadWrite
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!authorizeResult.Succeeded)
|
if (!authorizeResult.Succeeded)
|
||||||
{
|
{
|
||||||
throw new HttpApiException(
|
return Problem(
|
||||||
authorizeResult.Message ?? "No permission for the requested resource",
|
authorizeResult.Message ?? "No permission for the requested resource",
|
||||||
403
|
statusCode: 403
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonCore.Exceptions;
|
using MoonCore.Common;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Models;
|
|
||||||
using Moonlight.ApiServer.Database.Entities;
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.ApiServer.Mappers;
|
||||||
using MoonlightServers.ApiServer.Services;
|
using MoonlightServers.ApiServer.Services;
|
||||||
|
using MoonlightServers.Shared.Constants;
|
||||||
using MoonlightServers.Shared.Enums;
|
using MoonlightServers.Shared.Enums;
|
||||||
using MoonlightServers.Shared.Http.Requests.Client.Servers.Shares;
|
using MoonlightServers.Shared.Http.Requests.Client.Servers.Shares;
|
||||||
using MoonlightServers.Shared.Http.Responses.Client.Servers.Shares;
|
using MoonlightServers.Shared.Http.Responses.Client.Servers.Shares;
|
||||||
@@ -16,7 +16,7 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
|||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/client/servers")]
|
[Route("api/client/servers/{serverId:int}/shares")]
|
||||||
public class SharesController : Controller
|
public class SharesController : Controller
|
||||||
{
|
{
|
||||||
private readonly DatabaseRepository<Server> ServerRepository;
|
private readonly DatabaseRepository<Server> ServerRepository;
|
||||||
@@ -37,21 +37,32 @@ public class SharesController : Controller
|
|||||||
AuthorizeService = authorizeService;
|
AuthorizeService = authorizeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/shares")]
|
[HttpGet]
|
||||||
public async Task<PagedData<ServerShareResponse>> GetAll(
|
public async Task<ActionResult<CountedData<ServerShareResponse>>> GetAllAsync(
|
||||||
[FromRoute] int serverId,
|
[FromRoute] int serverId,
|
||||||
[FromQuery] [Range(0, int.MaxValue)] int page,
|
[FromQuery] int startIndex,
|
||||||
[FromQuery] [Range(1, 100)] int pageSize
|
[FromQuery] int count
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId);
|
if (count > 100)
|
||||||
|
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||||
|
|
||||||
|
var server = await GetServerByIdAsync(serverId);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
var query = ShareRepository
|
var query = ShareRepository
|
||||||
.Get()
|
.Get()
|
||||||
.Where(x => x.Server.Id == server.Id);
|
.Where(x => x.Server.Id == server.Value.Id);
|
||||||
|
|
||||||
var count = await query.CountAsync();
|
var totalCount = await query.CountAsync();
|
||||||
var items = await query.Skip(page * pageSize).Take(pageSize).ToArrayAsync();
|
|
||||||
|
var items = await query
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.Skip(startIndex)
|
||||||
|
.Take(count)
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
var userIds = items
|
var userIds = items
|
||||||
.Select(x => x.UserId)
|
.Select(x => x.UserId)
|
||||||
@@ -67,33 +78,33 @@ public class SharesController : Controller
|
|||||||
{
|
{
|
||||||
Id = x.Id,
|
Id = x.Id,
|
||||||
Username = users.First(y => y.Id == x.UserId).Username,
|
Username = users.First(y => y.Id == x.UserId).Username,
|
||||||
Permissions = x.Content.Permissions.ToArray()
|
Permissions = ShareMapper.MapToPermissionLevels(x.Content)
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
return new PagedData<ServerShareResponse>()
|
return new CountedData<ServerShareResponse>()
|
||||||
{
|
{
|
||||||
Items = mappedItems,
|
Items = mappedItems,
|
||||||
CurrentPage = page,
|
TotalCount = totalCount
|
||||||
PageSize = pageSize,
|
|
||||||
TotalItems = count,
|
|
||||||
TotalPages = count == 0 ? 0 : count / pageSize
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/shares/{id:int}")]
|
[HttpGet("{id:int}")]
|
||||||
public async Task<ServerShareResponse> Get(
|
public async Task<ActionResult<ServerShareResponse>> GetAsync(
|
||||||
[FromRoute] int serverId,
|
[FromRoute] int serverId,
|
||||||
[FromRoute] int id
|
[FromRoute] int id
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId);
|
var server = await GetServerByIdAsync(serverId);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
var share = await ShareRepository
|
var share = await ShareRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefaultAsync(x => x.Server.Id == server.Id && x.Id == id);
|
.FirstOrDefaultAsync(x => x.Server.Id == server.Value.Id && x.Id == id);
|
||||||
|
|
||||||
if (share == null)
|
if (share == null)
|
||||||
throw new HttpApiException("A share with that id cannot be found", 404);
|
return Problem("A share with that id cannot be found", statusCode: 404);
|
||||||
|
|
||||||
var user = await UserRepository
|
var user = await UserRepository
|
||||||
.Get()
|
.Get()
|
||||||
@@ -103,127 +114,135 @@ public class SharesController : Controller
|
|||||||
{
|
{
|
||||||
Id = share.Id,
|
Id = share.Id,
|
||||||
Username = user.Username,
|
Username = user.Username,
|
||||||
Permissions = share.Content.Permissions.ToArray()
|
Permissions = ShareMapper.MapToPermissionLevels(share.Content)
|
||||||
};
|
};
|
||||||
|
|
||||||
return mappedItem;
|
return mappedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/shares")]
|
[HttpPost]
|
||||||
public async Task<ServerShareResponse> Create(
|
public async Task<ActionResult<ServerShareResponse>> CreateAsync(
|
||||||
[FromRoute] int serverId,
|
[FromRoute] int serverId,
|
||||||
[FromBody] CreateShareRequest request
|
[FromBody] CreateShareRequest request
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId);
|
var server = await GetServerByIdAsync(serverId);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
var user = await UserRepository
|
var user = await UserRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefaultAsync(x => x.Username == request.Username);
|
.FirstOrDefaultAsync(x => x.Username == request.Username);
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
throw new HttpApiException("A user with that username could not be found", 400);
|
return Problem("A user with that username could not be found", statusCode: 400);
|
||||||
|
|
||||||
var share = new ServerShare()
|
var share = new ServerShare()
|
||||||
{
|
{
|
||||||
Server = server,
|
Server = server.Value,
|
||||||
Content = new()
|
Content = ShareMapper.MapToServerShareContent(request.Permissions),
|
||||||
{
|
|
||||||
Permissions = request.Permissions
|
|
||||||
},
|
|
||||||
CreatedAt = DateTime.UtcNow,
|
CreatedAt = DateTime.UtcNow,
|
||||||
UpdatedAt = DateTime.UtcNow,
|
UpdatedAt = DateTime.UtcNow,
|
||||||
UserId = user.Id
|
UserId = user.Id
|
||||||
};
|
};
|
||||||
|
|
||||||
var finalShare = await ShareRepository.Add(share);
|
var finalShare = await ShareRepository.AddAsync(share);
|
||||||
|
|
||||||
var mappedItem = new ServerShareResponse()
|
var mappedItem = new ServerShareResponse()
|
||||||
{
|
{
|
||||||
Id = finalShare.Id,
|
Id = finalShare.Id,
|
||||||
Username = user.Username,
|
Username = user.Username,
|
||||||
Permissions = finalShare.Content.Permissions.ToArray()
|
Permissions = ShareMapper.MapToPermissionLevels(finalShare.Content)
|
||||||
};
|
};
|
||||||
|
|
||||||
return mappedItem;
|
return mappedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPatch("{serverId:int}/shares/{id:int}")]
|
[HttpPatch("{id:int}")]
|
||||||
public async Task<ServerShareResponse> Update(
|
public async Task<ActionResult<ServerShareResponse>> UpdateAsync(
|
||||||
[FromRoute] int serverId,
|
[FromRoute] int serverId,
|
||||||
[FromRoute] int id,
|
[FromRoute] int id,
|
||||||
[FromBody] UpdateShareRequest request
|
[FromBody] UpdateShareRequest request
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId);
|
var server = await GetServerByIdAsync(serverId);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
var share = await ShareRepository
|
var share = await ShareRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefaultAsync(x => x.Server.Id == server.Id && x.Id == id);
|
.FirstOrDefaultAsync(x => x.Server.Id == server.Value.Id && x.Id == id);
|
||||||
|
|
||||||
if (share == null)
|
if (share == null)
|
||||||
throw new HttpApiException("A share with that id cannot be found", 404);
|
return Problem("A share with that id cannot be found", statusCode: 404);
|
||||||
|
|
||||||
|
share.Content = ShareMapper.MapToServerShareContent(request.Permissions);
|
||||||
|
|
||||||
share.Content.Permissions = request.Permissions;
|
|
||||||
share.UpdatedAt = DateTime.UtcNow;
|
share.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
await ShareRepository.Update(share);
|
await ShareRepository.UpdateAsync(share);
|
||||||
|
|
||||||
var user = await UserRepository
|
var user = await UserRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefaultAsync(x => x.Id == share.UserId);
|
.FirstOrDefaultAsync(x => x.Id == share.UserId);
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
throw new HttpApiException("A user with that id could not be found", 400);
|
return Problem("A user with that id could not be found", statusCode: 400);
|
||||||
|
|
||||||
var mappedItem = new ServerShareResponse()
|
var mappedItem = new ServerShareResponse()
|
||||||
{
|
{
|
||||||
Id = share.Id,
|
Id = share.Id,
|
||||||
Username = user.Username,
|
Username = user.Username,
|
||||||
Permissions = share.Content.Permissions.ToArray()
|
Permissions = ShareMapper.MapToPermissionLevels(share.Content)
|
||||||
};
|
};
|
||||||
|
|
||||||
return mappedItem;
|
return mappedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{serverId:int}/shares/{id:int}")]
|
[HttpDelete("{id:int}")]
|
||||||
public async Task Delete(
|
public async Task<ActionResult> DeleteAsync(
|
||||||
[FromRoute] int serverId,
|
[FromRoute] int serverId,
|
||||||
[FromRoute] int id
|
[FromRoute] int id
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId);
|
var server = await GetServerByIdAsync(serverId);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
var share = await ShareRepository
|
var share = await ShareRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefaultAsync(x => x.Server.Id == server.Id && x.Id == id);
|
.FirstOrDefaultAsync(x => x.Server.Id == server.Value.Id && x.Id == id);
|
||||||
|
|
||||||
if (share == null)
|
if (share == null)
|
||||||
throw new HttpApiException("A share with that id cannot be found", 404);
|
return Problem("A share with that id cannot be found", statusCode: 404);
|
||||||
|
|
||||||
await ShareRepository.Remove(share);
|
await ShareRepository.RemoveAsync(share);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Server> GetServerById(int serverId)
|
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId)
|
||||||
{
|
{
|
||||||
var server = await ServerRepository
|
var server = await ServerRepository
|
||||||
.Get()
|
.Get()
|
||||||
.Include(x => x.Node)
|
|
||||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
var authorizeResult = await AuthorizeService.Authorize(
|
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||||
User, server,
|
User, server,
|
||||||
permission => permission is { Name: "shares", Type: >= ServerPermissionType.ReadWrite }
|
ServerPermissionConstants.Shares,
|
||||||
|
ServerPermissionLevel.ReadWrite
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!authorizeResult.Succeeded)
|
if (!authorizeResult.Succeeded)
|
||||||
{
|
{
|
||||||
throw new HttpApiException(
|
return Problem(
|
||||||
authorizeResult.Message ?? "No permission for the requested resource",
|
authorizeResult.Message ?? "No permission for the requested resource",
|
||||||
403
|
statusCode: 403
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MoonCore.Common;
|
||||||
using MoonCore.Exceptions;
|
using MoonCore.Exceptions;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using Moonlight.ApiServer.Database.Entities;
|
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Services;
|
using MoonlightServers.ApiServer.Services;
|
||||||
|
using MoonlightServers.Shared.Constants;
|
||||||
using MoonlightServers.Shared.Enums;
|
using MoonlightServers.Shared.Enums;
|
||||||
using MoonlightServers.Shared.Http.Requests.Client.Servers.Variables;
|
using MoonlightServers.Shared.Http.Requests.Client.Servers.Variables;
|
||||||
using MoonlightServers.Shared.Http.Responses.Client.Servers.Variables;
|
using MoonlightServers.Shared.Http.Responses.Client.Servers.Variables;
|
||||||
@@ -14,29 +15,66 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
|||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/client/servers")]
|
[Route("api/client/servers/{serverId:int}/variables")]
|
||||||
public class VariablesController : Controller
|
public class VariablesController : Controller
|
||||||
{
|
{
|
||||||
private readonly DatabaseRepository<Server> ServerRepository;
|
private readonly DatabaseRepository<Server> ServerRepository;
|
||||||
|
private readonly DatabaseRepository<ServerVariable> ServerVariableRepository;
|
||||||
|
private readonly DatabaseRepository<StarVariable> StarVariableRepository;
|
||||||
private readonly ServerAuthorizeService AuthorizeService;
|
private readonly ServerAuthorizeService AuthorizeService;
|
||||||
|
|
||||||
public VariablesController(
|
public VariablesController(
|
||||||
DatabaseRepository<Server> serverRepository,
|
DatabaseRepository<Server> serverRepository,
|
||||||
ServerAuthorizeService authorizeService
|
ServerAuthorizeService authorizeService,
|
||||||
|
DatabaseRepository<ServerVariable> serverVariableRepository,
|
||||||
|
DatabaseRepository<StarVariable> starVariableRepository
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ServerRepository = serverRepository;
|
ServerRepository = serverRepository;
|
||||||
AuthorizeService = authorizeService;
|
AuthorizeService = authorizeService;
|
||||||
|
ServerVariableRepository = serverVariableRepository;
|
||||||
|
StarVariableRepository = starVariableRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/variables")]
|
[HttpGet]
|
||||||
public async Task<ServerVariableDetailResponse[]> Get([FromRoute] int serverId)
|
public async Task<ActionResult<CountedData<ServerVariableDetailResponse>>> GetAsync(
|
||||||
|
[FromRoute] int serverId,
|
||||||
|
[FromQuery] int startIndex,
|
||||||
|
[FromQuery] int count
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId, ServerPermissionType.Read);
|
if (count > 100)
|
||||||
|
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||||
|
|
||||||
|
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.Read);
|
||||||
|
|
||||||
|
if (server.Value == null)
|
||||||
|
return server.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
return server.Star.Variables.Select(starVariable =>
|
var query = StarVariableRepository
|
||||||
|
.Get()
|
||||||
|
.Where(x => x.Star.Id == server.Value.Star.Id);
|
||||||
|
|
||||||
|
var totalCount = await query.CountAsync();
|
||||||
|
|
||||||
|
var starVariables = await query
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.Skip(startIndex)
|
||||||
|
.Take(count)
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
var starVariableKeys = starVariables
|
||||||
|
.Select(x => x.Key)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var serverVariables = await ServerVariableRepository
|
||||||
|
.Get()
|
||||||
|
.Where(x => x.Server.Id == server.Value.Id && starVariableKeys.Contains(x.Key))
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
var responses = starVariables.Select(starVariable =>
|
||||||
{
|
{
|
||||||
var serverVariable = server.Variables.First(x => x.Key == starVariable.Key);
|
var serverVariable = serverVariables.First(x => x.Key == starVariable.Key);
|
||||||
|
|
||||||
return new ServerVariableDetailResponse()
|
return new ServerVariableDetailResponse()
|
||||||
{
|
{
|
||||||
@@ -48,17 +86,28 @@ public class VariablesController : Controller
|
|||||||
Filter = starVariable.Filter
|
Filter = starVariable.Filter
|
||||||
};
|
};
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
|
return new CountedData<ServerVariableDetailResponse>()
|
||||||
|
{
|
||||||
|
Items = responses,
|
||||||
|
TotalCount = totalCount
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{serverId:int}/variables")]
|
[HttpPut]
|
||||||
public async Task<ServerVariableDetailResponse> UpdateSingle(
|
public async Task<ActionResult<ServerVariableDetailResponse>> UpdateSingleAsync(
|
||||||
[FromRoute] int serverId,
|
[FromRoute] int serverId,
|
||||||
[FromBody] UpdateServerVariableRequest request
|
[FromBody] UpdateServerVariableRequest request
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// TODO: Handle filter
|
// TODO: Handle filter
|
||||||
|
|
||||||
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
|
var serverResult = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||||
|
|
||||||
|
if (serverResult.Value == null)
|
||||||
|
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
var server = serverResult.Value;
|
||||||
|
|
||||||
var serverVariable = server.Variables.FirstOrDefault(x => x.Key == request.Key);
|
var serverVariable = server.Variables.FirstOrDefault(x => x.Key == request.Key);
|
||||||
var starVariable = server.Star.Variables.FirstOrDefault(x => x.Key == request.Key);
|
var starVariable = server.Star.Variables.FirstOrDefault(x => x.Key == request.Key);
|
||||||
@@ -67,7 +116,7 @@ public class VariablesController : Controller
|
|||||||
throw new HttpApiException($"No variable with the key found: {request.Key}", 400);
|
throw new HttpApiException($"No variable with the key found: {request.Key}", 400);
|
||||||
|
|
||||||
serverVariable.Value = request.Value;
|
serverVariable.Value = request.Value;
|
||||||
await ServerRepository.Update(server);
|
await ServerRepository.UpdateAsync(server);
|
||||||
|
|
||||||
return new ServerVariableDetailResponse()
|
return new ServerVariableDetailResponse()
|
||||||
{
|
{
|
||||||
@@ -80,13 +129,18 @@ public class VariablesController : Controller
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPatch("{serverId:int}/variables")]
|
[HttpPatch]
|
||||||
public async Task<ServerVariableDetailResponse[]> Update(
|
public async Task<ActionResult<ServerVariableDetailResponse[]>> UpdateAsync(
|
||||||
[FromRoute] int serverId,
|
[FromRoute] int serverId,
|
||||||
[FromBody] UpdateServerVariableRangeRequest request
|
[FromBody] UpdateServerVariableRangeRequest request
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var server = await GetServerById(serverId, ServerPermissionType.ReadWrite);
|
var serverResult = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||||
|
|
||||||
|
if (serverResult.Value == null)
|
||||||
|
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||||
|
|
||||||
|
var server = serverResult.Value;
|
||||||
|
|
||||||
foreach (var variable in request.Variables)
|
foreach (var variable in request.Variables)
|
||||||
{
|
{
|
||||||
@@ -101,7 +155,7 @@ public class VariablesController : Controller
|
|||||||
serverVariable.Value = variable.Value;
|
serverVariable.Value = variable.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ServerRepository.Update(server);
|
await ServerRepository.UpdateAsync(server);
|
||||||
|
|
||||||
return request.Variables.Select(requestVariable =>
|
return request.Variables.Select(requestVariable =>
|
||||||
{
|
{
|
||||||
@@ -120,28 +174,29 @@ public class VariablesController : Controller
|
|||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Server> GetServerById(int serverId, ServerPermissionType type)
|
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId, ServerPermissionLevel level)
|
||||||
{
|
{
|
||||||
var server = await ServerRepository
|
var server = await ServerRepository
|
||||||
.Get()
|
.Get()
|
||||||
.Include(x => x.Variables)
|
|
||||||
.Include(x => x.Star)
|
.Include(x => x.Star)
|
||||||
.ThenInclude(x => x.Variables)
|
.ThenInclude(x => x.Variables)
|
||||||
|
.Include(x => x.Variables)
|
||||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
var authorizeResult = await AuthorizeService.Authorize(
|
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||||
User, server,
|
User, server,
|
||||||
permission => permission.Name == "variables" && permission.Type >= type
|
ServerPermissionConstants.Variables,
|
||||||
|
level
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!authorizeResult.Succeeded)
|
if (!authorizeResult.Succeeded)
|
||||||
{
|
{
|
||||||
throw new HttpApiException(
|
return Problem(
|
||||||
authorizeResult.Message ?? "No permission for the requested resource",
|
authorizeResult.Message ?? "No permission for the requested resource",
|
||||||
403
|
statusCode: 403
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Remote.Nodes;
|
|||||||
public class NodeTripController : Controller
|
public class NodeTripController : Controller
|
||||||
{
|
{
|
||||||
[HttpGet("trip")]
|
[HttpGet("trip")]
|
||||||
public Task Get() => Task.CompletedTask;
|
public Task GetAsync() => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MoonCore.Common;
|
||||||
using MoonCore.Exceptions;
|
using MoonCore.Exceptions;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Models;
|
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||||
|
|
||||||
@@ -30,8 +31,14 @@ public class ServersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<PagedData<ServerDataResponse>> Get([FromQuery] int page, [FromQuery] int pageSize)
|
public async Task<ActionResult<CountedData<ServerDataResponse>>> GetAsync(
|
||||||
|
[FromQuery] int startIndex,
|
||||||
|
[FromQuery] int count
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
if (count > 100)
|
||||||
|
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||||
|
|
||||||
// Load the node via the id
|
// Load the node via the id
|
||||||
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
|
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
|
||||||
|
|
||||||
@@ -51,8 +58,8 @@ public class ServersController : Controller
|
|||||||
.ThenInclude(x => x.DockerImages)
|
.ThenInclude(x => x.DockerImages)
|
||||||
.Include(x => x.Variables)
|
.Include(x => x.Variables)
|
||||||
.Include(x => x.Allocations)
|
.Include(x => x.Allocations)
|
||||||
.Skip(page * pageSize)
|
.Skip(startIndex)
|
||||||
.Take(pageSize)
|
.Take(count)
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
|
|
||||||
var serverData = new List<ServerDataResponse>();
|
var serverData = new List<ServerDataResponse>();
|
||||||
@@ -67,18 +74,15 @@ public class ServersController : Controller
|
|||||||
serverData.Add(convertedData);
|
serverData.Add(convertedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PagedData<ServerDataResponse>()
|
return new CountedData<ServerDataResponse>()
|
||||||
{
|
{
|
||||||
Items = serverData.ToArray(),
|
Items = serverData.ToArray(),
|
||||||
CurrentPage = page,
|
TotalCount = total
|
||||||
PageSize = pageSize,
|
|
||||||
TotalItems = total,
|
|
||||||
TotalPages = total == 0 ? 0 : total / pageSize
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id:int}")]
|
[HttpGet("{id:int}")]
|
||||||
public async Task<ServerDataResponse> Get([FromRoute] int id)
|
public async Task<ServerDataResponse> GetAsync([FromRoute] int id)
|
||||||
{
|
{
|
||||||
// Load the node via the id
|
// Load the node via the id
|
||||||
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
|
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
|
||||||
@@ -110,7 +114,7 @@ public class ServersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id:int}/install")]
|
[HttpGet("{id:int}/install")]
|
||||||
public async Task<ServerInstallDataResponse> GetInstall([FromRoute] int id)
|
public async Task<ServerInstallDataResponse> GetInstallAsync([FromRoute] int id)
|
||||||
{
|
{
|
||||||
// Load the node via the id
|
// Load the node via the id
|
||||||
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
|
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
|
||||||
@@ -179,7 +183,6 @@ public class ServersController : Controller
|
|||||||
Port = x.Port
|
Port = x.Port
|
||||||
}).ToArray(),
|
}).ToArray(),
|
||||||
Variables = server.Variables.ToDictionary(x => x.Key, x => x.Value),
|
Variables = server.Variables.ToDictionary(x => x.Key, x => x.Value),
|
||||||
Bandwidth = server.Bandwidth,
|
|
||||||
Cpu = server.Cpu,
|
Cpu = server.Cpu,
|
||||||
Disk = server.Disk,
|
Disk = server.Disk,
|
||||||
Memory = server.Memory,
|
Memory = server.Memory,
|
||||||
@@ -188,7 +191,6 @@ public class ServersController : Controller
|
|||||||
PullDockerImage = dockerImage.AutoPulling,
|
PullDockerImage = dockerImage.AutoPulling,
|
||||||
ParseConiguration = server.Star.ParseConfiguration,
|
ParseConiguration = server.Star.ParseConfiguration,
|
||||||
StopCommand = server.Star.StopCommand,
|
StopCommand = server.Star.StopCommand,
|
||||||
UseVirtualDisk = server.UseVirtualDisk
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using MoonCore.Attributes;
|
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Interfaces;
|
using MoonlightServers.ApiServer.Interfaces;
|
||||||
using MoonlightServers.ApiServer.Models;
|
using MoonlightServers.ApiServer.Models;
|
||||||
using MoonlightServers.Shared.Models;
|
using MoonlightServers.Shared.Enums;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
||||||
|
|
||||||
@@ -12,15 +11,18 @@ public class AdminAuthFilter : IServerAuthorizationFilter
|
|||||||
{
|
{
|
||||||
private readonly IAuthorizationService AuthorizationService;
|
private readonly IAuthorizationService AuthorizationService;
|
||||||
|
|
||||||
|
public int Priority => 0;
|
||||||
|
|
||||||
public AdminAuthFilter(IAuthorizationService authorizationService)
|
public AdminAuthFilter(IAuthorizationService authorizationService)
|
||||||
{
|
{
|
||||||
AuthorizationService = authorizationService;
|
AuthorizationService = authorizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerAuthorizationResult?> Process(
|
public async Task<ServerAuthorizationResult?> ProcessAsync(
|
||||||
ClaimsPrincipal user,
|
ClaimsPrincipal user,
|
||||||
Server server,
|
Server server,
|
||||||
Func<ServerSharePermission, bool>? filter = null
|
string permissionId,
|
||||||
|
ServerPermissionLevel requiredLevel
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var authResult = await AuthorizationService.AuthorizeAsync(
|
var authResult = await AuthorizationService.AuthorizeAsync(
|
||||||
|
|||||||
@@ -1,26 +1,32 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using MoonCore.Attributes;
|
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Interfaces;
|
using MoonlightServers.ApiServer.Interfaces;
|
||||||
using MoonlightServers.ApiServer.Models;
|
using MoonlightServers.ApiServer.Models;
|
||||||
using MoonlightServers.Shared.Models;
|
using MoonlightServers.Shared.Enums;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
||||||
|
|
||||||
public class OwnerAuthFilter : IServerAuthorizationFilter
|
public class OwnerAuthFilter : IServerAuthorizationFilter
|
||||||
{
|
{
|
||||||
public Task<ServerAuthorizationResult?> Process(ClaimsPrincipal user, Server server, Func<ServerSharePermission, bool>? filter = null)
|
public int Priority => 0;
|
||||||
|
|
||||||
|
public Task<ServerAuthorizationResult?> ProcessAsync(
|
||||||
|
ClaimsPrincipal user,
|
||||||
|
Server server,
|
||||||
|
string permissionId,
|
||||||
|
ServerPermissionLevel requiredLevel
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var userIdValue = user.FindFirstValue("userId");
|
var userIdValue = user.FindFirstValue("UserId");
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(userIdValue)) // This is the case for api keys
|
if (string.IsNullOrEmpty(userIdValue)) // This is the case for api keys
|
||||||
return Task.FromResult<ServerAuthorizationResult?>(null);
|
return Task.FromResult<ServerAuthorizationResult?>(null);
|
||||||
|
|
||||||
var userId = int.Parse(userIdValue);
|
var userId = int.Parse(userIdValue);
|
||||||
|
|
||||||
if(server.OwnerId != userId)
|
if (server.OwnerId != userId)
|
||||||
return Task.FromResult<ServerAuthorizationResult?>(null);
|
return Task.FromResult<ServerAuthorizationResult?>(null);
|
||||||
|
|
||||||
return Task.FromResult<ServerAuthorizationResult?>(
|
return Task.FromResult<ServerAuthorizationResult?>(
|
||||||
ServerAuthorizationResult.Success()
|
ServerAuthorizationResult.Success()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonCore.Attributes;
|
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Interfaces;
|
using MoonlightServers.ApiServer.Interfaces;
|
||||||
using MoonlightServers.ApiServer.Models;
|
using MoonlightServers.ApiServer.Models;
|
||||||
using MoonlightServers.Shared.Models;
|
using MoonlightServers.Shared.Enums;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
||||||
|
|
||||||
@@ -18,10 +17,13 @@ public class ShareAuthFilter : IServerAuthorizationFilter
|
|||||||
ShareRepository = shareRepository;
|
ShareRepository = shareRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerAuthorizationResult?> Process(
|
public int Priority => 0;
|
||||||
|
|
||||||
|
public async Task<ServerAuthorizationResult?> ProcessAsync(
|
||||||
ClaimsPrincipal user,
|
ClaimsPrincipal user,
|
||||||
Server server,
|
Server server,
|
||||||
Func<ServerSharePermission, bool>? filter = null
|
string permissionId,
|
||||||
|
ServerPermissionLevel requiredLevel
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var userIdValue = user.FindFirstValue("userId");
|
var userIdValue = user.FindFirstValue("userId");
|
||||||
@@ -30,18 +32,23 @@ public class ShareAuthFilter : IServerAuthorizationFilter
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
var userId = int.Parse(userIdValue);
|
var userId = int.Parse(userIdValue);
|
||||||
|
|
||||||
var share = await ShareRepository
|
var share = await ShareRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FirstOrDefaultAsync(x => x.Server.Id == server.Id && x.UserId == userId);
|
.FirstOrDefaultAsync(x => x.Server.Id == server.Id && x.UserId == userId);
|
||||||
|
|
||||||
if (share == null)
|
if (share == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if(filter == null)
|
if (string.IsNullOrEmpty(permissionId) || requiredLevel == ServerPermissionLevel.None)
|
||||||
return ServerAuthorizationResult.Success(share);
|
return ServerAuthorizationResult.Success(share);
|
||||||
|
|
||||||
|
var possiblePermShare = share.Content.Permissions.FirstOrDefault(x => x.Identifier == permissionId);
|
||||||
|
|
||||||
|
if (possiblePermShare == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
if(share.Content.Permissions.Any(filter))
|
if (possiblePermShare.Level >= requiredLevel)
|
||||||
return ServerAuthorizationResult.Success(share);
|
return ServerAuthorizationResult.Success(share);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Models;
|
using MoonlightServers.ApiServer.Models;
|
||||||
using MoonlightServers.Shared.Models;
|
using MoonlightServers.Shared.Enums;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Interfaces;
|
namespace MoonlightServers.ApiServer.Interfaces;
|
||||||
|
|
||||||
public interface IServerAuthorizationFilter
|
public interface IServerAuthorizationFilter
|
||||||
{
|
{
|
||||||
// Return null => skip to next filter / handler
|
// Return null => skip to next filter / handler
|
||||||
// Return any value, instant return
|
// Return any value, instant complete
|
||||||
public Task<ServerAuthorizationResult?> Process(
|
|
||||||
|
public int Priority { get; }
|
||||||
|
|
||||||
|
public Task<ServerAuthorizationResult?> ProcessAsync(
|
||||||
ClaimsPrincipal user,
|
ClaimsPrincipal user,
|
||||||
Server server,
|
Server server,
|
||||||
Func<ServerSharePermission, bool>? filter = null
|
string permissionId,
|
||||||
|
ServerPermissionLevel requiredLevel
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
18
MoonlightServers.ApiServer/Mappers/AllocationMapper.cs
Normal file
18
MoonlightServers.ApiServer/Mappers/AllocationMapper.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations;
|
||||||
|
using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations;
|
||||||
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
|
namespace MoonlightServers.ApiServer.Mappers;
|
||||||
|
|
||||||
|
[Mapper(AllowNullPropertyAssignment = false)]
|
||||||
|
public static partial class AllocationMapper
|
||||||
|
{
|
||||||
|
public static partial NodeAllocationResponse ToNodeAllocation(Allocation allocation);
|
||||||
|
public static partial Allocation ToAllocation(CreateNodeAllocationRequest request);
|
||||||
|
public static partial void Merge(UpdateNodeAllocationRequest request, Allocation allocation);
|
||||||
|
|
||||||
|
// EF Projections
|
||||||
|
|
||||||
|
public static partial IQueryable<NodeAllocationResponse> ProjectToAdminResponse(this IQueryable<Allocation> allocations);
|
||||||
|
}
|
||||||
18
MoonlightServers.ApiServer/Mappers/DockerImageMapper.cs
Normal file
18
MoonlightServers.ApiServer/Mappers/DockerImageMapper.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.Shared.Http.Requests.Admin.StarDockerImages;
|
||||||
|
using MoonlightServers.Shared.Http.Responses.Admin.StarDockerImages;
|
||||||
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
|
namespace MoonlightServers.ApiServer.Mappers;
|
||||||
|
|
||||||
|
[Mapper(AllowNullPropertyAssignment = false)]
|
||||||
|
public static partial class DockerImageMapper
|
||||||
|
{
|
||||||
|
public static partial StarDockerImageResponse ToAdminResponse(StarDockerImage dockerImage);
|
||||||
|
public static partial StarDockerImage ToDockerImage(CreateStarDockerImageRequest request);
|
||||||
|
public static partial void Merge(UpdateStarDockerImageRequest request, StarDockerImage variable);
|
||||||
|
|
||||||
|
// EF Migrations
|
||||||
|
|
||||||
|
public static partial IQueryable<StarDockerImageResponse> ProjectToAdminResponse(this IQueryable<StarDockerImage> dockerImages);
|
||||||
|
}
|
||||||
18
MoonlightServers.ApiServer/Mappers/NodeMapper.cs
Normal file
18
MoonlightServers.ApiServer/Mappers/NodeMapper.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.Shared.Http.Requests.Admin.Nodes;
|
||||||
|
using MoonlightServers.Shared.Http.Responses.Admin.Nodes;
|
||||||
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
|
namespace MoonlightServers.ApiServer.Mappers;
|
||||||
|
|
||||||
|
[Mapper(AllowNullPropertyAssignment = false)]
|
||||||
|
public static partial class NodeMapper
|
||||||
|
{
|
||||||
|
public static partial NodeResponse ToAdminNodeResponse(Node node);
|
||||||
|
public static partial Node ToNode(CreateNodeRequest request);
|
||||||
|
public static partial void Merge(UpdateNodeRequest request, Node node);
|
||||||
|
|
||||||
|
// EF Projections
|
||||||
|
|
||||||
|
public static partial IQueryable<NodeResponse> ProjectToAdminResponse(this IQueryable<Node> nodes);
|
||||||
|
}
|
||||||
30
MoonlightServers.ApiServer/Mappers/ServerMapper.cs
Normal file
30
MoonlightServers.ApiServer/Mappers/ServerMapper.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.Shared.Http.Requests.Admin.Servers;
|
||||||
|
using MoonlightServers.Shared.Http.Responses.Admin.Servers;
|
||||||
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
|
namespace MoonlightServers.ApiServer.Mappers;
|
||||||
|
|
||||||
|
[Mapper(AllowNullPropertyAssignment = false)]
|
||||||
|
public static partial class ServerMapper
|
||||||
|
{
|
||||||
|
[UserMapping(Default = true)]
|
||||||
|
public static ServerResponse ToAdminServerResponse(Server server)
|
||||||
|
{
|
||||||
|
var response = ToAdminServerResponse_Internal(server);
|
||||||
|
|
||||||
|
response.AllocationIds = server.Allocations.Select(x => x.Id).ToArray();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static partial ServerResponse ToAdminServerResponse_Internal(Server server);
|
||||||
|
|
||||||
|
[MapperIgnoreSource(nameof(CreateServerRequest.Variables))]
|
||||||
|
public static partial Server ToServer(CreateServerRequest request);
|
||||||
|
public static partial void Merge(UpdateServerRequest request, Server server);
|
||||||
|
|
||||||
|
// EF Projections
|
||||||
|
|
||||||
|
public static partial IQueryable<ServerResponse> ProjectToAdminResponse(this IQueryable<Server> servers);
|
||||||
|
}
|
||||||
15
MoonlightServers.ApiServer/Mappers/ServerVariableMapper.cs
Normal file
15
MoonlightServers.ApiServer/Mappers/ServerVariableMapper.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.Shared.Http.Responses.Admin.ServerVariables;
|
||||||
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
|
namespace MoonlightServers.ApiServer.Mappers;
|
||||||
|
|
||||||
|
[Mapper(AllowNullPropertyAssignment = false)]
|
||||||
|
public static partial class ServerVariableMapper
|
||||||
|
{
|
||||||
|
public static partial ServerVariableResponse ToAdminResponse(ServerVariable serverVariable);
|
||||||
|
|
||||||
|
// EF Projections
|
||||||
|
|
||||||
|
public static partial IQueryable<ServerVariableResponse> ProjectToAdminResponse(this IQueryable<ServerVariable> variables);
|
||||||
|
}
|
||||||
27
MoonlightServers.ApiServer/Mappers/ShareMapper.cs
Normal file
27
MoonlightServers.ApiServer/Mappers/ShareMapper.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using MoonlightServers.ApiServer.Models;
|
||||||
|
using MoonlightServers.Shared.Enums;
|
||||||
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
|
namespace MoonlightServers.ApiServer.Mappers;
|
||||||
|
|
||||||
|
[Mapper]
|
||||||
|
public static partial class ShareMapper
|
||||||
|
{
|
||||||
|
public static ServerShareContent MapToServerShareContent(Dictionary<string, ServerPermissionLevel> permissionLevels)
|
||||||
|
{
|
||||||
|
return new ServerShareContent()
|
||||||
|
{
|
||||||
|
Permissions = permissionLevels.Select(x => new ServerShareContent.SharePermission()
|
||||||
|
{
|
||||||
|
Identifier = x.Key,
|
||||||
|
Level = x.Value
|
||||||
|
}).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<string, ServerPermissionLevel> MapToPermissionLevels(
|
||||||
|
ServerShareContent content)
|
||||||
|
{
|
||||||
|
return content.Permissions.ToDictionary(x => x.Identifier, x => x.Level);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
MoonlightServers.ApiServer/Mappers/StarMapper.cs
Normal file
18
MoonlightServers.ApiServer/Mappers/StarMapper.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.Shared.Http.Requests.Admin.Stars;
|
||||||
|
using MoonlightServers.Shared.Http.Responses.Admin.Stars;
|
||||||
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
|
namespace MoonlightServers.ApiServer.Mappers;
|
||||||
|
|
||||||
|
[Mapper(AllowNullPropertyAssignment = false)]
|
||||||
|
public static partial class StarMapper
|
||||||
|
{
|
||||||
|
public static partial StarResponse ToAdminResponse(Star star);
|
||||||
|
public static partial Star ToStar(CreateStarRequest request);
|
||||||
|
public static partial void Merge(UpdateStarRequest request, Star star);
|
||||||
|
|
||||||
|
// EF Projections
|
||||||
|
|
||||||
|
public static partial IQueryable<StarResponse> ProjectToAdminResponse(this IQueryable<Star> stars);
|
||||||
|
}
|
||||||
18
MoonlightServers.ApiServer/Mappers/StarVariableMapper.cs
Normal file
18
MoonlightServers.ApiServer/Mappers/StarVariableMapper.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.Shared.Http.Requests.Admin.StarVariables;
|
||||||
|
using MoonlightServers.Shared.Http.Responses.Admin.StarVariables;
|
||||||
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
|
namespace MoonlightServers.ApiServer.Mappers;
|
||||||
|
|
||||||
|
[Mapper(AllowNullPropertyAssignment = false)]
|
||||||
|
public static partial class StarVariableMapper
|
||||||
|
{
|
||||||
|
public static partial StarVariableResponse ToAdminResponse(StarVariable variable);
|
||||||
|
public static partial StarVariable ToStarVariable(CreateStarVariableRequest request);
|
||||||
|
public static partial void Merge(UpdateStarVariableRequest request, StarVariable variable);
|
||||||
|
|
||||||
|
// EF Projections
|
||||||
|
|
||||||
|
public static partial IQueryable<StarVariableResponse> ProjectToAdminResponse(this IQueryable<StarVariable> variables);
|
||||||
|
}
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
using MoonlightServers.Shared.Models;
|
using MoonlightServers.Shared.Enums;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Models;
|
namespace MoonlightServers.ApiServer.Models;
|
||||||
|
|
||||||
public class ServerShareContent
|
public record ServerShareContent
|
||||||
{
|
{
|
||||||
public List<ServerSharePermission> Permissions { get; set; } = [];
|
public List<SharePermission> Permissions { get; set; } = new();
|
||||||
|
|
||||||
|
public record SharePermission
|
||||||
|
{
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
public ServerPermissionLevel Level { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
@@ -11,37 +11,26 @@
|
|||||||
<PackageId>MoonlightServers.ApiServer</PackageId>
|
<PackageId>MoonlightServers.ApiServer</PackageId>
|
||||||
<Title>MoonlightServers.ApiServer</Title>
|
<Title>MoonlightServers.ApiServer</Title>
|
||||||
<Version>2.1.0</Version>
|
<Version>2.1.0</Version>
|
||||||
<PackageTags>apiserver</PackageTags>
|
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Moonlight.ApiServer" Version="2.1.0"/>
|
<PackageReference Include="Moonlight.ApiServer" Version="2.1.*" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5"/>
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MoonlightServers.DaemonShared\MoonlightServers.DaemonShared.csproj"/>
|
<ProjectReference Include="..\MoonlightServers.DaemonShared\MoonlightServers.DaemonShared.csproj"/>
|
||||||
<ProjectReference Include="..\MoonlightServers.Frontend\MoonlightServers.Frontend.csproj"/>
|
|
||||||
<ProjectReference Include="..\MoonlightServers.Shared\MoonlightServers.Shared.csproj"/>
|
<ProjectReference Include="..\MoonlightServers.Shared\MoonlightServers.Shared.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Database\Migrations\"/>
|
<Folder Include="Database\Migrations\"/>
|
||||||
<Folder Include="Http\Middleware\"/>
|
<Folder Include="Http\Middleware\"/>
|
||||||
|
<Folder Include="Startup\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Label="Build instruction for nuget package building">
|
<ItemGroup Label="Build instruction for nuget package building">
|
||||||
<None Include="**\*.cs" Exclude="storage\**\*;bin\**\*;obj\**\*">
|
|
||||||
<Pack>true</Pack>
|
|
||||||
<PackagePath>src</PackagePath>
|
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Include="**\*.razor" Exclude="storage\**\*;bin\**\*;obj\**\*">
|
|
||||||
<Pack>true</Pack>
|
|
||||||
<PackagePath>src</PackagePath>
|
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<Compile Remove="storage\**\*"/>
|
<Compile Remove="storage\**\*"/>
|
||||||
<Content Remove="storage\**\*"/>
|
<Content Remove="storage\**\*"/>
|
||||||
<None Remove="storage\**\*"/>
|
<None Remove="storage\**\*"/>
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using MoonCore.Extensions;
|
using MoonCore.Extensions;
|
||||||
using Moonlight.ApiServer.Configuration;
|
using Moonlight.ApiServer.Configuration;
|
||||||
using Moonlight.ApiServer.Models;
|
using Moonlight.ApiServer.Models;
|
||||||
@@ -7,12 +10,11 @@ using MoonlightServers.ApiServer.Helpers;
|
|||||||
using MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
using MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
||||||
using MoonlightServers.ApiServer.Interfaces;
|
using MoonlightServers.ApiServer.Interfaces;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Startup;
|
namespace MoonlightServers.ApiServer;
|
||||||
|
|
||||||
[PluginStartup]
|
|
||||||
public class PluginStartup : IPluginStartup
|
public class PluginStartup : IPluginStartup
|
||||||
{
|
{
|
||||||
public Task BuildApplication(IServiceProvider serviceProvider, IHostApplicationBuilder builder)
|
public void AddPlugin(WebApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
// Scan the current plugin assembly for di services
|
// Scan the current plugin assembly for di services
|
||||||
builder.Services.AutoAddServices<PluginStartup>();
|
builder.Services.AutoAddServices<PluginStartup>();
|
||||||
@@ -24,33 +26,34 @@ public class PluginStartup : IPluginStartup
|
|||||||
.AddAuthentication()
|
.AddAuthentication()
|
||||||
.AddScheme<NodeAuthOptions, NodeAuthScheme>("nodeAuthentication", null);
|
.AddScheme<NodeAuthOptions, NodeAuthScheme>("nodeAuthentication", null);
|
||||||
|
|
||||||
var configuration = serviceProvider.GetRequiredService<AppConfiguration>();
|
var configuration = AppConfiguration.CreateEmpty();
|
||||||
|
builder.Configuration.Bind(configuration);
|
||||||
|
|
||||||
if (configuration.Client.Enable)
|
if (configuration.Frontend.EnableHosting)
|
||||||
{
|
{
|
||||||
builder.Services.AddSingleton(new FrontendConfigurationOption()
|
builder.Services.AddSingleton(new FrontendConfigurationOption()
|
||||||
{
|
{
|
||||||
Scripts =
|
Scripts =
|
||||||
[
|
[
|
||||||
"js/XtermBlazor.min.js",
|
"/_content/MoonlightServers.Frontend/js/XtermBlazor.min.js",
|
||||||
"js/addon-fit.js",
|
"/_content/MoonlightServers.Frontend/js/addon-fit.js",
|
||||||
"js/moonlightServers.js"
|
"/_content/MoonlightServers.Frontend/js/moonlightServers.js"
|
||||||
],
|
],
|
||||||
Styles = ["css/XtermBlazor.min.css"]
|
Styles = ["/_content/MoonlightServers.Frontend/css/XtermBlazor.min.css"]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add server auth filters
|
// Add server auth filters
|
||||||
builder.Services.AddSingleton<IServerAuthorizationFilter, OwnerAuthFilter>();
|
builder.Services.AddSingleton<IServerAuthorizationFilter, OwnerAuthFilter>();
|
||||||
builder.Services.AddScoped<IServerAuthorizationFilter, AdminAuthFilter>();
|
builder.Services.AddScoped<IServerAuthorizationFilter, AdminAuthFilter>();
|
||||||
builder.Services.AddScoped<IServerAuthorizationFilter, ShareAuthFilter>();
|
builder.Services.AddScoped<IServerAuthorizationFilter, ShareAuthFilter>();
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ConfigureApplication(IServiceProvider serviceProvider, IApplicationBuilder app)
|
public void UsePlugin(WebApplication app)
|
||||||
=> Task.CompletedTask;
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public Task ConfigureEndpoints(IServiceProvider serviceProvider, IEndpointRouteBuilder routeBuilder)
|
public void MapPlugin(WebApplication app)
|
||||||
=> Task.CompletedTask;
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using Moonlight.ApiServer;
|
|
||||||
using Moonlight.ApiServer.Models;
|
|
||||||
using MoonlightServers.ApiServer.Startup;
|
|
||||||
|
|
||||||
// Development Server Startup
|
|
||||||
|
|
||||||
// This file is a small helper for development instances for moonlight.
|
|
||||||
// It calls the moonlight startup with the current project loaded as a plugin.
|
|
||||||
// This allows you to develop and debug projects without any hassle
|
|
||||||
|
|
||||||
// !!! DO NOT HARDCORE ANY SECRETS HERE !!!
|
|
||||||
|
|
||||||
var startup = new Startup();
|
|
||||||
|
|
||||||
await startup.Run(args, [
|
|
||||||
new PluginStartup()
|
|
||||||
]);
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
namespace MoonlightServers.ApiServer.Services;
|
|
||||||
|
|
||||||
public class NodeBootService : IHostedLifecycleService
|
|
||||||
{
|
|
||||||
public async Task StartedAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
// TODO: Add node boot calls here
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Unused
|
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
|
||||||
=> Task.CompletedTask;
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
=> Task.CompletedTask;
|
|
||||||
|
|
||||||
public Task StartingAsync(CancellationToken cancellationToken)
|
|
||||||
=> Task.CompletedTask;
|
|
||||||
|
|
||||||
public Task StoppedAsync(CancellationToken cancellationToken)
|
|
||||||
=> Task.CompletedTask;
|
|
||||||
|
|
||||||
public Task StoppingAsync(CancellationToken cancellationToken)
|
|
||||||
=> Task.CompletedTask;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
using System.IdentityModel.Tokens.Jwt;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using MoonCore.Attributes;
|
using MoonCore.Attributes;
|
||||||
using MoonCore.Extended.Helpers;
|
|
||||||
using MoonCore.Helpers;
|
using MoonCore.Helpers;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics;
|
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics;
|
||||||
@@ -41,7 +39,7 @@ public class NodeService
|
|||||||
return jwtSecurityTokenHandler.WriteToken(securityToken);
|
return jwtSecurityTokenHandler.WriteToken(securityToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SystemStatusResponse> GetSystemStatus(Node node)
|
public async Task<SystemStatusResponse> GetSystemStatusAsync(Node node)
|
||||||
{
|
{
|
||||||
using var apiClient = CreateApiClient(node);
|
using var apiClient = CreateApiClient(node);
|
||||||
return await apiClient.GetJson<SystemStatusResponse>("api/system/status");
|
return await apiClient.GetJson<SystemStatusResponse>("api/system/status");
|
||||||
@@ -49,13 +47,13 @@ public class NodeService
|
|||||||
|
|
||||||
#region Statistics
|
#region Statistics
|
||||||
|
|
||||||
public async Task<StatisticsResponse> GetStatistics(Node node)
|
public async Task<StatisticsResponse> GetStatisticsAsync(Node node)
|
||||||
{
|
{
|
||||||
using var apiClient = CreateApiClient(node);
|
using var apiClient = CreateApiClient(node);
|
||||||
return await apiClient.GetJson<StatisticsResponse>("api/statistics");
|
return await apiClient.GetJson<StatisticsResponse>("api/statistics");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<StatisticsDockerResponse> GetDockerStatistics(Node node)
|
public async Task<StatisticsDockerResponse> GetDockerStatisticsAsync(Node node)
|
||||||
{
|
{
|
||||||
using var apiClient = CreateApiClient(node);
|
using var apiClient = CreateApiClient(node);
|
||||||
return await apiClient.GetJson<StatisticsDockerResponse>("api/statistics/docker");
|
return await apiClient.GetJson<StatisticsDockerResponse>("api/statistics/docker");
|
||||||
|
|||||||
@@ -3,36 +3,42 @@ using MoonCore.Attributes;
|
|||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Interfaces;
|
using MoonlightServers.ApiServer.Interfaces;
|
||||||
using MoonlightServers.ApiServer.Models;
|
using MoonlightServers.ApiServer.Models;
|
||||||
using MoonlightServers.Shared.Models;
|
using MoonlightServers.Shared.Enums;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Services;
|
namespace MoonlightServers.ApiServer.Services;
|
||||||
|
|
||||||
[Scoped]
|
[Scoped]
|
||||||
public class ServerAuthorizeService
|
public class ServerAuthorizeService
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<IServerAuthorizationFilter> AuthorizationFilters;
|
private readonly IServerAuthorizationFilter[] AuthorizationFilters;
|
||||||
|
|
||||||
public ServerAuthorizeService(
|
public ServerAuthorizeService(
|
||||||
IEnumerable<IServerAuthorizationFilter> authorizationFilters
|
IEnumerable<IServerAuthorizationFilter> authorizationFilters
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
AuthorizationFilters = authorizationFilters;
|
AuthorizationFilters = authorizationFilters.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerAuthorizationResult> Authorize(
|
public async Task<ServerAuthorizationResult> AuthorizeAsync(
|
||||||
ClaimsPrincipal user,
|
ClaimsPrincipal user,
|
||||||
Server server,
|
Server server,
|
||||||
Func<ServerSharePermission, bool>? filter = null
|
string permissionIdentifier,
|
||||||
|
ServerPermissionLevel permissionLevel
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
foreach (var authorizationFilter in AuthorizationFilters)
|
foreach (var authorizationFilter in AuthorizationFilters)
|
||||||
{
|
{
|
||||||
var result = await authorizationFilter.Process(user, server, filter);
|
var result = await authorizationFilter.ProcessAsync(
|
||||||
|
user,
|
||||||
|
server,
|
||||||
|
permissionIdentifier,
|
||||||
|
permissionLevel
|
||||||
|
);
|
||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ServerAuthorizationResult.Failed();
|
return ServerAuthorizationResult.Failed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,45 +24,54 @@ public class ServerFileSystemService
|
|||||||
ServerRepository = serverRepository;
|
ServerRepository = serverRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerFileSystemResponse[]> List(Server server, string path)
|
public async Task<ServerFileSystemResponse[]> ListAsync(Server server, string path)
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
|
|
||||||
return await apiClient.GetJson<ServerFileSystemResponse[]>(
|
return await apiClient.GetJson<ServerFileSystemResponse[]>(
|
||||||
$"api/servers/{server.Id}/files/list?path={path}"
|
$"api/servers/{server.Id}/files/list?path={path}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Move(Server server, string oldPath, string newPath)
|
public async Task MoveAsync(Server server, string oldPath, string newPath)
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
|
|
||||||
await apiClient.Post(
|
await apiClient.Post(
|
||||||
$"api/servers/{server.Id}/files/move?oldPath={oldPath}&newPath={newPath}"
|
$"api/servers/{server.Id}/files/move?oldPath={oldPath}&newPath={newPath}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Delete(Server server, string path)
|
public async Task DeleteAsync(Server server, string path)
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
|
|
||||||
await apiClient.Delete(
|
await apiClient.Delete(
|
||||||
$"api/servers/{server.Id}/files/delete?path={path}"
|
$"api/servers/{server.Id}/files/delete?path={path}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Mkdir(Server server, string path)
|
public async Task MkdirAsync(Server server, string path)
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
|
|
||||||
await apiClient.Post(
|
await apiClient.Post(
|
||||||
$"api/servers/{server.Id}/files/mkdir?path={path}"
|
$"api/servers/{server.Id}/files/mkdir?path={path}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Compress(Server server, CompressType type, string[] items, string destination)
|
public async Task TouchAsync(Server server, string path)
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
|
|
||||||
|
await apiClient.Post(
|
||||||
|
$"api/servers/{server.Id}/files/touch?path={path}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CompressAsync(Server server, CompressType type, string[] items, string destination)
|
||||||
|
{
|
||||||
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
|
|
||||||
await apiClient.Post(
|
await apiClient.Post(
|
||||||
$"api/servers/{server.Id}/files/compress",
|
$"api/servers/{server.Id}/files/compress",
|
||||||
@@ -75,9 +84,9 @@ public class ServerFileSystemService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Decompress(Server server, CompressType type, string path, string destination)
|
public async Task DecompressAsync(Server server, CompressType type, string path, string destination)
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
|
|
||||||
await apiClient.Post(
|
await apiClient.Post(
|
||||||
$"api/servers/{server.Id}/files/decompress",
|
$"api/servers/{server.Id}/files/decompress",
|
||||||
@@ -92,7 +101,7 @@ public class ServerFileSystemService
|
|||||||
|
|
||||||
#region Helpers
|
#region Helpers
|
||||||
|
|
||||||
private async Task<HttpApiClient> GetApiClient(Server server)
|
private async Task<HttpApiClient> GetApiClientAsync(Server server)
|
||||||
{
|
{
|
||||||
var serverWithNode = server;
|
var serverWithNode = server;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoonCore.Attributes;
|
using MoonCore.Attributes;
|
||||||
using MoonCore.Exceptions;
|
using MoonCore.Exceptions;
|
||||||
@@ -6,6 +5,7 @@ using MoonCore.Extended.Abstractions;
|
|||||||
using MoonCore.Helpers;
|
using MoonCore.Helpers;
|
||||||
using Moonlight.ApiServer.Database.Entities;
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.DaemonShared.DaemonSide.Http.Requests;
|
||||||
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
|
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Services;
|
namespace MoonlightServers.ApiServer.Services;
|
||||||
@@ -24,11 +24,11 @@ public class ServerService
|
|||||||
|
|
||||||
#region Power Actions
|
#region Power Actions
|
||||||
|
|
||||||
public async Task Start(Server server)
|
public async Task StartAsync(Server server)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
await apiClient.Post($"api/servers/{server.Id}/start");
|
await apiClient.Post($"api/servers/{server.Id}/start");
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
@@ -36,12 +36,12 @@ public class ServerService
|
|||||||
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Stop(Server server)
|
public async Task StopAsync(Server server)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
await apiClient.Post($"api/servers/{server.Id}/stop");
|
await apiClient.Post($"api/servers/{server.Id}/stop");
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
@@ -49,12 +49,12 @@ public class ServerService
|
|||||||
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Kill(Server server)
|
public async Task KillAsync(Server server)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
await apiClient.Post($"api/servers/{server.Id}/kill");
|
await apiClient.Post($"api/servers/{server.Id}/kill");
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
@@ -64,12 +64,12 @@ public class ServerService
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public async Task Install(Server server)
|
public async Task InstallAsync(Server server)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
await apiClient.Post($"api/servers/{server.Id}/install");
|
await apiClient.Post($"api/servers/{server.Id}/install");
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
@@ -78,11 +78,11 @@ public class ServerService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Sync(Server server)
|
public async Task SyncAsync(Server server)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
await apiClient.Post($"api/servers/{server.Id}/sync");
|
await apiClient.Post($"api/servers/{server.Id}/sync");
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
@@ -90,12 +90,12 @@ public class ServerService
|
|||||||
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SyncDelete(Server server)
|
public async Task SyncDeleteAsync(Server server)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
await apiClient.Delete($"api/servers/{server.Id}");
|
await apiClient.Delete($"api/servers/{server.Id}");
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
@@ -103,12 +103,12 @@ public class ServerService
|
|||||||
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerStatusResponse> GetStatus(Server server)
|
public async Task<ServerStatusResponse> GetStatusAsync(Server server)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
return await apiClient.GetJson<ServerStatusResponse>($"api/servers/{server.Id}/status");
|
return await apiClient.GetJson<ServerStatusResponse>($"api/servers/{server.Id}/status");
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
@@ -117,11 +117,11 @@ public class ServerService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerLogsResponse> GetLogs(Server server)
|
public async Task<ServerLogsResponse> GetLogsAsync(Server server)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
return await apiClient.GetJson<ServerLogsResponse>($"api/servers/{server.Id}/logs");
|
return await apiClient.GetJson<ServerLogsResponse>($"api/servers/{server.Id}/logs");
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
@@ -130,14 +130,34 @@ public class ServerService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerStatsResponse> GetStats(Server server)
|
public async Task<ServerStatsResponse> GetStatsAsync(Server server)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var apiClient = await GetApiClient(server);
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
return await apiClient.GetJson<ServerStatsResponse>($"api/servers/{server.Id}/stats");
|
return await apiClient.GetJson<ServerStatsResponse>($"api/servers/{server.Id}/stats");
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException)
|
||||||
|
{
|
||||||
|
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RunCommandAsync(Server server, string command)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var apiClient = await GetApiClientAsync(server);
|
||||||
|
|
||||||
|
await apiClient.Post(
|
||||||
|
$"api/servers/{server.Id}/command",
|
||||||
|
new ServerCommandRequest()
|
||||||
|
{
|
||||||
|
Command = command
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (HttpRequestException)
|
||||||
{
|
{
|
||||||
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
||||||
}
|
}
|
||||||
@@ -150,14 +170,10 @@ public class ServerService
|
|||||||
if (server.OwnerId == user.Id)
|
if (server.OwnerId == user.Id)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var permissions = JsonSerializer.Deserialize<string[]>(
|
return PermissionHelper.HasPermission(user.Permissions, "admin.servers.get");
|
||||||
user.PermissionsJson
|
|
||||||
) ?? [];
|
|
||||||
|
|
||||||
return PermissionHelper.HasPermission(permissions, "admin.servers.get");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HttpApiClient> GetApiClient(Server server)
|
private async Task<HttpApiClient> GetApiClientAsync(Server server)
|
||||||
{
|
{
|
||||||
var serverWithNode = server;
|
var serverWithNode = server;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using MoonCore.Attributes;
|
using MoonCore.Attributes;
|
||||||
using MoonCore.Exceptions;
|
using MoonCore.Exceptions;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
@@ -22,7 +23,7 @@ public class StarImportExportService
|
|||||||
Logger = logger;
|
Logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> Export(int id)
|
public async Task<string> ExportAsync(int id)
|
||||||
{
|
{
|
||||||
var star = StarRepository
|
var star = StarRepository
|
||||||
.Get()
|
.Get()
|
||||||
@@ -77,20 +78,20 @@ public class StarImportExportService
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Star> Import(string json)
|
public async Task<Star> ImportAsync(string json)
|
||||||
{
|
{
|
||||||
// Determine which importer to use based on simple patterns
|
// Determine which importer to use based on simple patterns
|
||||||
if (json.Contains("RequiredAllocations"))
|
if (json.Contains("RequiredAllocations"))
|
||||||
return await ImportStar(json);
|
return await ImportStarAsync(json);
|
||||||
else if (json.Contains("AllocationsNeeded"))
|
else if (json.Contains("AllocationsNeeded"))
|
||||||
return await ImportImage(json);
|
return await ImportImageAsync(json);
|
||||||
else if (json.Contains("_comment"))
|
else if (json.Contains("_comment"))
|
||||||
return await ImportEgg(json);
|
return await ImportEggAsync(json);
|
||||||
else
|
else
|
||||||
throw new HttpApiException("Unable to determine the format of the imported star/image/egg", 400);
|
throw new HttpApiException("Unable to determine the format of the imported star/image/egg", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Star> ImportStar(string json)
|
public async Task<Star> ImportStarAsync(string json)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -137,7 +138,7 @@ public class StarImportExportService
|
|||||||
}).ToList()
|
}).ToList()
|
||||||
};
|
};
|
||||||
|
|
||||||
var finalStar = await StarRepository.Add(star);
|
var finalStar = await StarRepository.AddAsync(star);
|
||||||
|
|
||||||
return finalStar;
|
return finalStar;
|
||||||
}
|
}
|
||||||
@@ -148,7 +149,7 @@ public class StarImportExportService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Star> ImportImage(string json)
|
public async Task<Star> ImportImageAsync(string json)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -234,7 +235,7 @@ public class StarImportExportService
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
var finalStar = await StarRepository.Add(star);
|
var finalStar = await StarRepository.AddAsync(star);
|
||||||
|
|
||||||
return finalStar;
|
return finalStar;
|
||||||
}
|
}
|
||||||
@@ -245,7 +246,7 @@ public class StarImportExportService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Star> ImportEgg(string json)
|
public async Task<Star> ImportEggAsync(string json)
|
||||||
{
|
{
|
||||||
// Create result
|
// Create result
|
||||||
var star = new Star();
|
var star = new Star();
|
||||||
@@ -402,7 +403,7 @@ public class StarImportExportService
|
|||||||
star.AllowDockerImageChange = true;
|
star.AllowDockerImageChange = true;
|
||||||
|
|
||||||
// Finally save it to the db
|
// Finally save it to the db
|
||||||
var finalStar = await StarRepository.Add(star);
|
var finalStar = await StarRepository.AddAsync(star);
|
||||||
|
|
||||||
return finalStar;
|
return finalStar;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using MoonCore.Helpers;
|
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.Configuration;
|
namespace MoonlightServers.Daemon.Configuration;
|
||||||
|
|
||||||
public class AppConfiguration
|
public class AppConfiguration
|
||||||
@@ -44,10 +42,10 @@ public class AppConfiguration
|
|||||||
|
|
||||||
public class StorageData
|
public class StorageData
|
||||||
{
|
{
|
||||||
public string Volumes { get; set; } = PathBuilder.Dir("volumes");
|
public string Volumes { get; set; } = Path.Combine("storage", "volumes");
|
||||||
public string VirtualDisks { get; set; } = PathBuilder.Dir("virtualDisks");
|
public string VirtualDisks { get; set; } = Path.Combine("storage", "virtualDisks");
|
||||||
public string Backups { get; set; } = PathBuilder.Dir("backups");
|
public string Backups { get; set; } = Path.Combine("storage", "backups");
|
||||||
public string Install { get; set; } = PathBuilder.Dir("install");
|
public string Install { get; set; } =Path.Combine("storage", "install");
|
||||||
|
|
||||||
public VirtualDiskData VirtualDiskOptions { get; set; } = new();
|
public VirtualDiskData VirtualDiskOptions { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ public static class ServerConfigurationExtensions
|
|||||||
Variables = response.Variables,
|
Variables = response.Variables,
|
||||||
OnlineDetection = response.OnlineDetection,
|
OnlineDetection = response.OnlineDetection,
|
||||||
DockerImage = response.DockerImage,
|
DockerImage = response.DockerImage,
|
||||||
UseVirtualDisk = response.UseVirtualDisk,
|
|
||||||
Bandwidth = response.Bandwidth,
|
|
||||||
Cpu = response.Cpu,
|
Cpu = response.Cpu,
|
||||||
Disk = response.Disk,
|
Disk = response.Disk,
|
||||||
Memory = response.Memory,
|
Memory = response.Memory,
|
||||||
|
|||||||
31
MoonlightServers.Daemon/Helpers/CompositeServiceProvider.cs
Normal file
31
MoonlightServers.Daemon/Helpers/CompositeServiceProvider.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
namespace MoonlightServers.Daemon.Helpers;
|
||||||
|
|
||||||
|
public class CompositeServiceProvider : IServiceProvider
|
||||||
|
{
|
||||||
|
private readonly List<IServiceProvider> ServiceProviders;
|
||||||
|
|
||||||
|
public CompositeServiceProvider(params IServiceProvider[] serviceProviders)
|
||||||
|
{
|
||||||
|
ServiceProviders = new List<IServiceProvider>(serviceProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? GetService(Type serviceType)
|
||||||
|
{
|
||||||
|
foreach (var provider in ServiceProviders)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var service = provider.GetService(serviceType);
|
||||||
|
|
||||||
|
if (service != null)
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,18 +57,18 @@ public class HostSystemHelper
|
|||||||
|
|
||||||
#region CPU Usage
|
#region CPU Usage
|
||||||
|
|
||||||
public async Task<CpuUsageDetails> GetCpuUsage()
|
public async Task<CpuUsageDetails> GetCpuUsageAsync()
|
||||||
{
|
{
|
||||||
var result = new CpuUsageDetails();
|
var result = new CpuUsageDetails();
|
||||||
var perCoreUsages = new List<double>();
|
var perCoreUsages = new List<double>();
|
||||||
|
|
||||||
// Initial read
|
// Initial read
|
||||||
var (cpuLastStats, cpuLastSums) = await ReadAllCpuStats();
|
var (cpuLastStats, cpuLastSums) = await ReadAllCpuStatsAsync();
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
// Second read
|
// Second read
|
||||||
var (cpuNowStats, cpuNowSums) = await ReadAllCpuStats();
|
var (cpuNowStats, cpuNowSums) = await ReadAllCpuStatsAsync();
|
||||||
|
|
||||||
for (var i = 0; i < cpuNowStats.Length; i++)
|
for (var i = 0; i < cpuNowStats.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -94,7 +94,7 @@ public class HostSystemHelper
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(long[][] cpuStatsList, long[] cpuSums)> ReadAllCpuStats()
|
private async Task<(long[][] cpuStatsList, long[] cpuSums)> ReadAllCpuStatsAsync()
|
||||||
{
|
{
|
||||||
var lines = await File.ReadAllLinesAsync("/proc/stat");
|
var lines = await File.ReadAllLinesAsync("/proc/stat");
|
||||||
|
|
||||||
@@ -128,12 +128,12 @@ public class HostSystemHelper
|
|||||||
|
|
||||||
#region Memory
|
#region Memory
|
||||||
|
|
||||||
public async Task ClearCachedMemory()
|
public async Task ClearCachedMemoryAsync()
|
||||||
{
|
{
|
||||||
await File.WriteAllTextAsync("/proc/sys/vm/drop_caches", "3");
|
await File.WriteAllTextAsync("/proc/sys/vm/drop_caches", "3");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MemoryUsageDetails> GetMemoryUsage()
|
public async Task<MemoryUsageDetails> GetMemoryUsageAsync()
|
||||||
{
|
{
|
||||||
var details = new MemoryUsageDetails();
|
var details = new MemoryUsageDetails();
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ public class HostSystemHelper
|
|||||||
|
|
||||||
#region Disks
|
#region Disks
|
||||||
|
|
||||||
public async Task<DiskUsageDetails[]> GetDiskUsages()
|
public async Task<DiskUsageDetails[]> GetDiskUsagesAsync()
|
||||||
{
|
{
|
||||||
var details = new List<DiskUsageDetails>();
|
var details = new List<DiskUsageDetails>();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using ICSharpCode.SharpZipLib.GZip;
|
|||||||
using ICSharpCode.SharpZipLib.Tar;
|
using ICSharpCode.SharpZipLib.Tar;
|
||||||
using ICSharpCode.SharpZipLib.Zip;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
using Mono.Unix.Native;
|
using Mono.Unix.Native;
|
||||||
using MoonCore.Unix.Exceptions;
|
|
||||||
using MoonCore.Unix.SecureFs;
|
using MoonCore.Unix.SecureFs;
|
||||||
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
|
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
|
||||||
using MoonlightServers.DaemonShared.Enums;
|
using MoonlightServers.DaemonShared.Enums;
|
||||||
@@ -19,7 +18,7 @@ public class ServerFileSystem
|
|||||||
FileSystem = fileSystem;
|
FileSystem = fileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ServerFileSystemResponse[]> List(string inputPath)
|
public Task<ServerFileSystemResponse[]> ListAsync(string inputPath)
|
||||||
{
|
{
|
||||||
var path = Normalize(inputPath);
|
var path = Normalize(inputPath);
|
||||||
var entries = FileSystem.ReadDir(path);
|
var entries = FileSystem.ReadDir(path);
|
||||||
@@ -30,12 +29,12 @@ public class ServerFileSystem
|
|||||||
// to hide the folder shown by virtual disk volumes
|
// to hide the folder shown by virtual disk volumes
|
||||||
if (string.IsNullOrEmpty(inputPath) || inputPath == "/")
|
if (string.IsNullOrEmpty(inputPath) || inputPath == "/")
|
||||||
entryQuery = entryQuery.Where(x => x.Name != "lost+found");
|
entryQuery = entryQuery.Where(x => x.Name != "lost+found");
|
||||||
|
|
||||||
var result = entryQuery
|
var result = entryQuery
|
||||||
.Select(x => new ServerFileSystemResponse()
|
.Select(x => new ServerFileSystemResponse()
|
||||||
{
|
{
|
||||||
Name = x.Name,
|
Name = x.Name,
|
||||||
IsFile = x.IsFile,
|
IsFolder = x.IsDirectory,
|
||||||
Size = x.Size,
|
Size = x.Size,
|
||||||
UpdatedAt = x.LastChanged,
|
UpdatedAt = x.LastChanged,
|
||||||
CreatedAt = x.CreatedAt
|
CreatedAt = x.CreatedAt
|
||||||
@@ -45,7 +44,7 @@ public class ServerFileSystem
|
|||||||
return Task.FromResult(result);
|
return Task.FromResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Move(string inputOldPath, string inputNewPath)
|
public Task MoveAsync(string inputOldPath, string inputNewPath)
|
||||||
{
|
{
|
||||||
var oldPath = Normalize(inputOldPath);
|
var oldPath = Normalize(inputOldPath);
|
||||||
var newPath = Normalize(inputNewPath);
|
var newPath = Normalize(inputNewPath);
|
||||||
@@ -55,7 +54,7 @@ public class ServerFileSystem
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Delete(string inputPath)
|
public Task DeleteAsync(string inputPath)
|
||||||
{
|
{
|
||||||
var path = Normalize(inputPath);
|
var path = Normalize(inputPath);
|
||||||
|
|
||||||
@@ -64,7 +63,7 @@ public class ServerFileSystem
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Mkdir(string inputPath)
|
public Task MkdirAsync(string inputPath)
|
||||||
{
|
{
|
||||||
var path = Normalize(inputPath);
|
var path = Normalize(inputPath);
|
||||||
|
|
||||||
@@ -73,7 +72,7 @@ public class ServerFileSystem
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CreateChunk(string inputPath, long totalSize, long positionToSkip, Stream chunkStream)
|
public Task TouchAsync(string inputPath)
|
||||||
{
|
{
|
||||||
var path = Normalize(inputPath);
|
var path = Normalize(inputPath);
|
||||||
|
|
||||||
@@ -81,14 +80,32 @@ public class ServerFileSystem
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
||||||
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
||||||
|
|
||||||
|
FileSystem.OpenFileWrite(
|
||||||
|
path,
|
||||||
|
_ => { },
|
||||||
|
OpenFlags.O_CREAT
|
||||||
|
); // We use these custom flags to ensure we aren't overwriting the file
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task CreateChunkAsync(string inputPath, long totalSize, long positionToSkip, Stream chunkStream)
|
||||||
|
{
|
||||||
|
var path = Normalize(inputPath);
|
||||||
|
|
||||||
|
var parentDirectory = Path.GetDirectoryName(path);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
||||||
|
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
||||||
|
|
||||||
FileSystem.OpenFileWrite(path, fileStream =>
|
FileSystem.OpenFileWrite(path, fileStream =>
|
||||||
{
|
{
|
||||||
if (fileStream.Length != totalSize)
|
if (fileStream.Length != totalSize)
|
||||||
fileStream.SetLength(totalSize);
|
fileStream.SetLength(totalSize);
|
||||||
|
|
||||||
fileStream.Position = positionToSkip;
|
fileStream.Position = positionToSkip;
|
||||||
|
|
||||||
chunkStream.CopyTo(fileStream);
|
chunkStream.CopyTo(fileStream);
|
||||||
fileStream.Flush();
|
fileStream.Flush();
|
||||||
}, OpenFlags.O_CREAT | OpenFlags.O_RDWR); // We use these custom flags to ensure we aren't overwriting the file
|
}, OpenFlags.O_CREAT | OpenFlags.O_RDWR); // We use these custom flags to ensure we aren't overwriting the file
|
||||||
@@ -96,7 +113,7 @@ public class ServerFileSystem
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Create(string inputPath, Stream dataStream)
|
public Task CreateAsync(string inputPath, Stream dataStream)
|
||||||
{
|
{
|
||||||
var path = Normalize(inputPath);
|
var path = Normalize(inputPath);
|
||||||
|
|
||||||
@@ -116,7 +133,7 @@ public class ServerFileSystem
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Read(string inputPath, Func<Stream, Task> onHandle)
|
public Task ReadAsync(string inputPath, Func<Stream, Task> onHandle)
|
||||||
{
|
{
|
||||||
var path = Normalize(inputPath);
|
var path = Normalize(inputPath);
|
||||||
|
|
||||||
@@ -131,7 +148,7 @@ public class ServerFileSystem
|
|||||||
|
|
||||||
#region Compression
|
#region Compression
|
||||||
|
|
||||||
public Task Compress(string[] itemsInput, string destinationInput, CompressType type)
|
public Task CompressAsync(string[] itemsInput, string destinationInput, CompressType type)
|
||||||
{
|
{
|
||||||
var destination = Normalize(destinationInput);
|
var destination = Normalize(destinationInput);
|
||||||
var items = itemsInput.Select(Normalize);
|
var items = itemsInput.Select(Normalize);
|
||||||
@@ -173,7 +190,7 @@ public class ServerFileSystem
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Decompress(string pathInput, string destinationInput, CompressType type)
|
public Task DecompressAsync(string pathInput, string destinationInput, CompressType type)
|
||||||
{
|
{
|
||||||
var path = Normalize(pathInput);
|
var path = Normalize(pathInput);
|
||||||
var destination = Normalize(destinationInput);
|
var destination = Normalize(destinationInput);
|
||||||
@@ -183,7 +200,7 @@ public class ServerFileSystem
|
|||||||
FileSystem.OpenFileRead(path, fileStream =>
|
FileSystem.OpenFileRead(path, fileStream =>
|
||||||
{
|
{
|
||||||
var zipInputStream = new ZipInputStream(fileStream);
|
var zipInputStream = new ZipInputStream(fileStream);
|
||||||
|
|
||||||
ExtractZip(zipInputStream, destination);
|
ExtractZip(zipInputStream, destination);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -193,11 +210,11 @@ public class ServerFileSystem
|
|||||||
{
|
{
|
||||||
var gzInputStream = new GZipInputStream(fileStream);
|
var gzInputStream = new GZipInputStream(fileStream);
|
||||||
var zipInputStream = new TarInputStream(gzInputStream, Encoding.UTF8);
|
var zipInputStream = new TarInputStream(gzInputStream, Encoding.UTF8);
|
||||||
|
|
||||||
ExtractTar(zipInputStream, destination);
|
ExtractTar(zipInputStream, destination);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,10 +276,7 @@ public class ServerFileSystem
|
|||||||
|
|
||||||
outputStream.PutNextEntry(entry);
|
outputStream.PutNextEntry(entry);
|
||||||
|
|
||||||
FileSystem.OpenFileRead(path, stream =>
|
FileSystem.OpenFileRead(path, stream => { stream.CopyTo(outputStream); });
|
||||||
{
|
|
||||||
stream.CopyTo(outputStream);
|
|
||||||
});
|
|
||||||
|
|
||||||
outputStream.CloseEntry();
|
outputStream.CloseEntry();
|
||||||
}
|
}
|
||||||
@@ -273,54 +287,54 @@ public class ServerFileSystem
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var entry = inputStream.GetNextEntry();
|
var entry = inputStream.GetNextEntry();
|
||||||
|
|
||||||
if(entry == null)
|
if (entry == null)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if(entry.IsDirectory)
|
if (entry.IsDirectory)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var fileDestination = Path.Combine(destination, entry.Name);
|
var fileDestination = Path.Combine(destination, entry.Name);
|
||||||
|
|
||||||
var parentDirectory = Path.GetDirectoryName(fileDestination);
|
var parentDirectory = Path.GetDirectoryName(fileDestination);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
||||||
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
||||||
|
|
||||||
FileSystem.OpenFileWrite(fileDestination, stream =>
|
FileSystem.OpenFileWrite(fileDestination, stream =>
|
||||||
{
|
{
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
|
|
||||||
inputStream.CopyTo(stream);
|
inputStream.CopyTo(stream);
|
||||||
|
|
||||||
stream.Flush();
|
stream.Flush();
|
||||||
}); // This will override the file if it exists
|
}); // This will override the file if it exists
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExtractTar(TarInputStream inputStream, string destination)
|
private void ExtractTar(TarInputStream inputStream, string destination)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var entry = inputStream.GetNextEntry();
|
var entry = inputStream.GetNextEntry();
|
||||||
|
|
||||||
if(entry == null)
|
if (entry == null)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if(entry.IsDirectory)
|
if (entry.IsDirectory)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var fileDestination = Path.Combine(destination, entry.Name);
|
var fileDestination = Path.Combine(destination, entry.Name);
|
||||||
|
|
||||||
var parentDirectory = Path.GetDirectoryName(fileDestination);
|
var parentDirectory = Path.GetDirectoryName(fileDestination);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
||||||
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
||||||
|
|
||||||
FileSystem.OpenFileWrite(fileDestination, stream =>
|
FileSystem.OpenFileWrite(fileDestination, stream =>
|
||||||
{
|
{
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
|
|
||||||
inputStream.CopyTo(stream);
|
inputStream.CopyTo(stream);
|
||||||
|
|
||||||
stream.Flush();
|
stream.Flush();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class UnsafeDockerClient
|
|||||||
Configuration = configuration;
|
Configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<HttpClient> CreateHttpClient()
|
public Task<HttpClient> CreateHttpClientAsync()
|
||||||
{
|
{
|
||||||
var client = new HttpClient(new SocketsHttpHandler()
|
var client = new HttpClient(new SocketsHttpHandler()
|
||||||
{
|
{
|
||||||
@@ -35,9 +35,9 @@ public class UnsafeDockerClient
|
|||||||
return Task.FromResult(client);
|
return Task.FromResult(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DataUsageResponse> GetDataUsage()
|
public async Task<DataUsageResponse> GetDataUsageAsync()
|
||||||
{
|
{
|
||||||
using var client = await CreateHttpClient();
|
using var client = await CreateHttpClientAsync();
|
||||||
var responseJson = await client.GetStringAsync("http://some.random.domain/v1.47/system/df");
|
var responseJson = await client.GetStringAsync("http://some.random.domain/v1.47/system/df");
|
||||||
var response = JsonSerializer.Deserialize<DataUsageResponse>(responseJson)!;
|
var response = JsonSerializer.Deserialize<DataUsageResponse>(responseJson)!;
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using MoonCore.Exceptions;
|
|
||||||
using MoonlightServers.Daemon.ServerSystem.SubSystems;
|
|
||||||
using MoonlightServers.Daemon.Services;
|
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/servers/download")]
|
|
||||||
[Authorize(AuthenticationSchemes = "accessToken", Policy = "serverDownload")]
|
|
||||||
public class DownloadController : Controller
|
|
||||||
{
|
|
||||||
private readonly ServerService ServerService;
|
|
||||||
|
|
||||||
public DownloadController(ServerService serverService)
|
|
||||||
{
|
|
||||||
ServerService = serverService;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
public async Task Download()
|
|
||||||
{
|
|
||||||
var serverId = int.Parse(User.Claims.First(x => x.Type == "serverId").Value);
|
|
||||||
var path = User.Claims.First(x => x.Type == "path").Value;
|
|
||||||
|
|
||||||
var server = ServerService.Find(serverId);
|
|
||||||
|
|
||||||
if (server == null)
|
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
|
||||||
|
|
||||||
var storageSubSystem = server.GetRequiredSubSystem<StorageSubSystem>();
|
|
||||||
|
|
||||||
var fileSystem = await storageSubSystem.GetFileSystem();
|
|
||||||
|
|
||||||
await fileSystem.Read(
|
|
||||||
path,
|
|
||||||
async dataStream =>
|
|
||||||
{
|
|
||||||
await Results.File(dataStream).ExecuteAsync(HttpContext);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Enums;
|
||||||
|
using MoonlightServers.Daemon.Services;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/servers/{id:int}")]
|
||||||
|
public class PowerController : Controller
|
||||||
|
{
|
||||||
|
private readonly ServerService ServerService;
|
||||||
|
|
||||||
|
public PowerController(ServerService serverService)
|
||||||
|
{
|
||||||
|
ServerService = serverService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("start")]
|
||||||
|
public async Task<ActionResult> StartAsync([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var server = ServerService.GetById(id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
if (!server.StateMachine.CanFire(ServerTrigger.Start))
|
||||||
|
return Problem("Cannot fire start trigger in this state");
|
||||||
|
|
||||||
|
await server.StateMachine.FireAsync(ServerTrigger.Start);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("stop")]
|
||||||
|
public async Task<ActionResult> StopAsync([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var server = ServerService.GetById(id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
if (!server.StateMachine.CanFire(ServerTrigger.Stop))
|
||||||
|
return Problem("Cannot fire stop trigger in this state");
|
||||||
|
|
||||||
|
await server.StateMachine.FireAsync(ServerTrigger.Stop);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("kill")]
|
||||||
|
public async Task<ActionResult> KillAsync([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var server = ServerService.GetById(id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
if (!server.StateMachine.CanFire(ServerTrigger.Kill))
|
||||||
|
return Problem("Cannot fire kill trigger in this state");
|
||||||
|
|
||||||
|
await server.StateMachine.FireAsync(ServerTrigger.Kill);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("install")]
|
||||||
|
public async Task<ActionResult> InstallAsync([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var server = ServerService.GetById(id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
|
if (!server.StateMachine.CanFire(ServerTrigger.Install))
|
||||||
|
return Problem("Cannot fire install trigger in this state");
|
||||||
|
|
||||||
|
await server.StateMachine.FireAsync(ServerTrigger.Install);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using MoonCore.Exceptions;
|
|
||||||
using MoonlightServers.Daemon.Helpers;
|
|
||||||
using MoonlightServers.Daemon.ServerSystem.SubSystems;
|
|
||||||
using MoonlightServers.Daemon.Services;
|
|
||||||
using MoonlightServers.DaemonShared.DaemonSide.Http.Requests;
|
|
||||||
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
|
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
|
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/servers")]
|
|
||||||
public class ServerFileSystemController : Controller
|
|
||||||
{
|
|
||||||
private readonly ServerService ServerService;
|
|
||||||
|
|
||||||
public ServerFileSystemController(ServerService serverService)
|
|
||||||
{
|
|
||||||
ServerService = serverService;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/files/list")]
|
|
||||||
public async Task<ServerFileSystemResponse[]> List([FromRoute] int id, [FromQuery] string path = "")
|
|
||||||
{
|
|
||||||
var fileSystem = await GetFileSystemById(id);
|
|
||||||
|
|
||||||
return await fileSystem.List(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("{id:int}/files/move")]
|
|
||||||
public async Task Move([FromRoute] int id, [FromQuery] string oldPath, [FromQuery] string newPath)
|
|
||||||
{
|
|
||||||
var fileSystem = await GetFileSystemById(id);
|
|
||||||
|
|
||||||
await fileSystem.Move(oldPath, newPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("{id:int}/files/delete")]
|
|
||||||
public async Task Delete([FromRoute] int id, [FromQuery] string path)
|
|
||||||
{
|
|
||||||
var fileSystem = await GetFileSystemById(id);
|
|
||||||
|
|
||||||
await fileSystem.Delete(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("{id:int}/files/mkdir")]
|
|
||||||
public async Task Mkdir([FromRoute] int id, [FromQuery] string path)
|
|
||||||
{
|
|
||||||
var fileSystem = await GetFileSystemById(id);
|
|
||||||
|
|
||||||
await fileSystem.Mkdir(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("{id:int}/files/compress")]
|
|
||||||
public async Task Compress([FromRoute] int id, [FromBody] ServerFilesCompressRequest request)
|
|
||||||
{
|
|
||||||
var fileSystem = await GetFileSystemById(id);
|
|
||||||
|
|
||||||
await fileSystem.Compress(
|
|
||||||
request.Items,
|
|
||||||
request.Destination,
|
|
||||||
request.Type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("{id:int}/files/decompress")]
|
|
||||||
public async Task Decompress([FromRoute] int id, [FromBody] ServerFilesDecompressRequest request)
|
|
||||||
{
|
|
||||||
var fileSystem = await GetFileSystemById(id);
|
|
||||||
|
|
||||||
await fileSystem.Decompress(
|
|
||||||
request.Path,
|
|
||||||
request.Destination,
|
|
||||||
request.Type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<ServerFileSystem> GetFileSystemById(int serverId)
|
|
||||||
{
|
|
||||||
var server = ServerService.Find(serverId);
|
|
||||||
|
|
||||||
if (server == null)
|
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
|
||||||
|
|
||||||
var storageSubSystem = server.GetRequiredSubSystem<StorageSubSystem>();
|
|
||||||
|
|
||||||
return await storageSubSystem.GetFileSystem();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using MoonCore.Exceptions;
|
|
||||||
using MoonlightServers.Daemon.Enums;
|
|
||||||
using MoonlightServers.Daemon.Services;
|
|
||||||
using ServerTrigger = MoonlightServers.Daemon.ServerSystem.ServerTrigger;
|
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
|
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/servers")]
|
|
||||||
public class ServerPowerController : Controller
|
|
||||||
{
|
|
||||||
private readonly ServerService ServerService;
|
|
||||||
|
|
||||||
public ServerPowerController(ServerService serverService)
|
|
||||||
{
|
|
||||||
ServerService = serverService;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/start")]
|
|
||||||
public async Task Start(int serverId)
|
|
||||||
{
|
|
||||||
var server = ServerService.Find(serverId);
|
|
||||||
|
|
||||||
if (server == null)
|
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
|
||||||
|
|
||||||
await server.Trigger(ServerTrigger.Start);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/stop")]
|
|
||||||
public async Task Stop(int serverId)
|
|
||||||
{
|
|
||||||
var server = ServerService.Find(serverId);
|
|
||||||
|
|
||||||
if (server == null)
|
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
|
||||||
|
|
||||||
await server.Trigger(ServerTrigger.Stop);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/install")]
|
|
||||||
public async Task Install(int serverId)
|
|
||||||
{
|
|
||||||
var server = ServerService.Find(serverId);
|
|
||||||
|
|
||||||
if (server == null)
|
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
|
||||||
|
|
||||||
await server.Trigger(ServerTrigger.Install);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/kill")]
|
|
||||||
public async Task Kill(int serverId)
|
|
||||||
{
|
|
||||||
var server = ServerService.Find(serverId);
|
|
||||||
|
|
||||||
if (server == null)
|
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
|
||||||
|
|
||||||
await server.Trigger(ServerTrigger.Kill);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +1,67 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using MoonCore.Exceptions;
|
using MoonlightServers.Daemon.Mappers;
|
||||||
using MoonlightServers.Daemon.ServerSystem.SubSystems;
|
|
||||||
using MoonlightServers.Daemon.Services;
|
using MoonlightServers.Daemon.Services;
|
||||||
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
|
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
|
||||||
using MoonlightServers.DaemonShared.Enums;
|
using MoonlightServers.DaemonShared.Enums;
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
|
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/servers")]
|
[Route("api/servers/{id:int}")]
|
||||||
public class ServersController : Controller
|
public class ServersController : Controller
|
||||||
{
|
{
|
||||||
private readonly ServerService ServerService;
|
private readonly ServerService ServerService;
|
||||||
|
private readonly ServerConfigurationMapper ConfigurationMapper;
|
||||||
|
|
||||||
public ServersController(ServerService serverService)
|
public ServersController(ServerService serverService, ServerConfigurationMapper configurationMapper)
|
||||||
{
|
{
|
||||||
ServerService = serverService;
|
ServerService = serverService;
|
||||||
|
ConfigurationMapper = configurationMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{serverId:int}/sync")]
|
[HttpPost("sync")]
|
||||||
public async Task Sync([FromRoute] int serverId)
|
public async Task<ActionResult> SyncAsync([FromRoute] int id)
|
||||||
{
|
{
|
||||||
await ServerService.Sync(serverId);
|
await ServerService.InitializeByIdAsync(id);
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{serverId:int}")]
|
[HttpGet("status")]
|
||||||
public async Task Delete([FromRoute] int serverId)
|
public async Task<ActionResult<ServerStatusResponse>> StatusAsync([FromRoute] int id)
|
||||||
{
|
{
|
||||||
await ServerService.Delete(serverId);
|
var server = ServerService.GetById(id);
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/status")]
|
|
||||||
public Task<ServerStatusResponse> GetStatus([FromRoute] int serverId)
|
|
||||||
{
|
|
||||||
var server = ServerService.Find(serverId);
|
|
||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
var result = new ServerStatusResponse()
|
return new ServerStatusResponse()
|
||||||
{
|
{
|
||||||
State = (ServerState)server.StateMachine.State
|
State = (ServerState)server.StateMachine.State
|
||||||
};
|
};
|
||||||
|
|
||||||
return Task.FromResult(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/logs")]
|
[HttpGet("logs")]
|
||||||
public async Task<ServerLogsResponse> GetLogs([FromRoute] int serverId)
|
public async Task<ActionResult<ServerLogsResponse>> LogsAsync([FromRoute] int id)
|
||||||
{
|
{
|
||||||
var server = ServerService.Find(serverId);
|
var server = ServerService.GetById(id);
|
||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
return Problem("No server with this id found", statusCode: 404);
|
||||||
|
|
||||||
var consoleSubSystem = server.GetRequiredSubSystem<ConsoleSubSystem>();
|
var messages = await server.Console.GetCacheAsync();
|
||||||
var messages = await consoleSubSystem.RetrieveCache();
|
|
||||||
|
|
||||||
return new ServerLogsResponse()
|
return new ServerLogsResponse()
|
||||||
{
|
{
|
||||||
Messages = messages
|
Messages = messages.ToArray()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{serverId:int}/stats")]
|
[HttpGet("stats")]
|
||||||
public Task<ServerStatsResponse> GetStats([FromRoute] int serverId)
|
public async Task<ServerStatsResponse> GetStatsAsync([FromRoute] int id)
|
||||||
{
|
{
|
||||||
var server = ServerService.Find(serverId);
|
return new ServerStatsResponse()
|
||||||
|
|
||||||
if (server == null)
|
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
|
||||||
|
|
||||||
var statsSubSystem = server.GetRequiredSubSystem<StatsSubSystem>();
|
|
||||||
|
|
||||||
return Task.FromResult<ServerStatsResponse>(new()
|
|
||||||
{
|
{
|
||||||
CpuUsage = statsSubSystem.CurrentStats.CpuUsage,
|
|
||||||
MemoryUsage = statsSubSystem.CurrentStats.MemoryUsage,
|
};
|
||||||
NetworkRead = statsSubSystem.CurrentStats.NetworkRead,
|
|
||||||
NetworkWrite = statsSubSystem.CurrentStats.NetworkWrite,
|
|
||||||
IoRead = statsSubSystem.CurrentStats.IoRead,
|
|
||||||
IoWrite = statsSubSystem.CurrentStats.IoWrite
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using MoonCore.Exceptions;
|
|
||||||
using MoonCore.Helpers;
|
|
||||||
using MoonlightServers.Daemon.Configuration;
|
|
||||||
using MoonlightServers.Daemon.ServerSystem.SubSystems;
|
|
||||||
using MoonlightServers.Daemon.Services;
|
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/servers/upload")]
|
|
||||||
[Authorize(AuthenticationSchemes = "accessToken", Policy = "serverUpload")]
|
|
||||||
public class UploadController : Controller
|
|
||||||
{
|
|
||||||
private readonly AppConfiguration Configuration;
|
|
||||||
private readonly ServerService ServerService;
|
|
||||||
|
|
||||||
public UploadController(
|
|
||||||
ServerService serverService,
|
|
||||||
AppConfiguration configuration
|
|
||||||
)
|
|
||||||
{
|
|
||||||
ServerService = serverService;
|
|
||||||
Configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
public async Task Upload(
|
|
||||||
[FromQuery] long totalSize,
|
|
||||||
[FromQuery] int chunkId,
|
|
||||||
[FromQuery] string path
|
|
||||||
)
|
|
||||||
{
|
|
||||||
var chunkSize = ByteConverter.FromMegaBytes(Configuration.Files.UploadChunkSize).Bytes;
|
|
||||||
var uploadLimit = ByteConverter.FromMegaBytes(Configuration.Files.UploadSizeLimit).Bytes;
|
|
||||||
|
|
||||||
#region File validation
|
|
||||||
|
|
||||||
if (Request.Form.Files.Count != 1)
|
|
||||||
throw new HttpApiException("You need to provide exactly one file", 400);
|
|
||||||
|
|
||||||
var file = Request.Form.Files[0];
|
|
||||||
|
|
||||||
if (file.Length > chunkSize)
|
|
||||||
throw new HttpApiException("The provided data exceeds the chunk size limit", 400);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
var serverId = int.Parse(User.Claims.First(x => x.Type == "serverId").Value);
|
|
||||||
|
|
||||||
#region Chunk calculation and validation
|
|
||||||
|
|
||||||
if(totalSize > uploadLimit)
|
|
||||||
throw new HttpApiException("Invalid upload request: Exceeding upload limit", 400);
|
|
||||||
|
|
||||||
var chunks = totalSize / chunkSize;
|
|
||||||
chunks += totalSize % chunkSize > 0 ? 1 : 0;
|
|
||||||
|
|
||||||
if (chunkId > chunks)
|
|
||||||
throw new HttpApiException("Invalid chunk id: Out of bounds", 400);
|
|
||||||
|
|
||||||
var positionToSkipTo = chunkSize * chunkId;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
var server = ServerService.Find(serverId);
|
|
||||||
|
|
||||||
if (server == null)
|
|
||||||
throw new HttpApiException("No server with this id found", 404);
|
|
||||||
|
|
||||||
var storageSubSystem = server.GetRequiredSubSystem<StorageSubSystem>();
|
|
||||||
|
|
||||||
var fileSystem = await storageSubSystem.GetFileSystem();
|
|
||||||
|
|
||||||
var dataStream = file.OpenReadStream();
|
|
||||||
|
|
||||||
await fileSystem.CreateChunk(
|
|
||||||
path,
|
|
||||||
totalSize,
|
|
||||||
positionToSkipTo,
|
|
||||||
dataStream
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,17 +18,17 @@ public class StatisticsController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<StatisticsResponse> Get()
|
public async Task<StatisticsResponse> GetAsync()
|
||||||
{
|
{
|
||||||
var response = new StatisticsResponse();
|
var response = new StatisticsResponse();
|
||||||
|
|
||||||
var cpuUsage = await HostSystemHelper.GetCpuUsage();
|
var cpuUsage = await HostSystemHelper.GetCpuUsageAsync();
|
||||||
|
|
||||||
response.Cpu.Model = cpuUsage.Model;
|
response.Cpu.Model = cpuUsage.Model;
|
||||||
response.Cpu.Usage = cpuUsage.OverallUsage;
|
response.Cpu.Usage = cpuUsage.OverallUsage;
|
||||||
response.Cpu.UsagePerCore = cpuUsage.PerCoreUsage;
|
response.Cpu.UsagePerCore = cpuUsage.PerCoreUsage;
|
||||||
|
|
||||||
var memoryUsage = await HostSystemHelper.GetMemoryUsage();
|
var memoryUsage = await HostSystemHelper.GetMemoryUsageAsync();
|
||||||
|
|
||||||
response.Memory.Available = memoryUsage.Available;
|
response.Memory.Available = memoryUsage.Available;
|
||||||
response.Memory.Cached = memoryUsage.Cached;
|
response.Memory.Cached = memoryUsage.Cached;
|
||||||
@@ -37,7 +37,7 @@ public class StatisticsController : Controller
|
|||||||
response.Memory.SwapTotal = memoryUsage.SwapTotal;
|
response.Memory.SwapTotal = memoryUsage.SwapTotal;
|
||||||
response.Memory.SwapFree = memoryUsage.SwapFree;
|
response.Memory.SwapFree = memoryUsage.SwapFree;
|
||||||
|
|
||||||
var diskDetails = await HostSystemHelper.GetDiskUsages();
|
var diskDetails = await HostSystemHelper.GetDiskUsagesAsync();
|
||||||
|
|
||||||
response.Disks = diskDetails.Select(x => new StatisticsResponse.DiskData()
|
response.Disks = diskDetails.Select(x => new StatisticsResponse.DiskData()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ public class StatisticsDockerController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<StatisticsDockerResponse> Get()
|
public async Task<StatisticsDockerResponse> GetAsync()
|
||||||
{
|
{
|
||||||
var usage = await DockerInfoService.GetDataUsage();
|
var usage = await DockerInfoService.GetDataUsageAsync();
|
||||||
|
|
||||||
return new StatisticsDockerResponse
|
return new StatisticsDockerResponse
|
||||||
{
|
{
|
||||||
Version = await DockerInfoService.GetDockerVersion(),
|
Version = await DockerInfoService.GetDockerVersionAsync(),
|
||||||
ContainersReclaimable = usage.Containers.Reclaimable,
|
ContainersReclaimable = usage.Containers.Reclaimable,
|
||||||
ContainersUsed = usage.Containers.Used,
|
ContainersUsed = usage.Containers.Used,
|
||||||
BuildCacheReclaimable = usage.BuildCache.Reclaimable,
|
BuildCacheReclaimable = usage.BuildCache.Reclaimable,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class SystemStatusController : Controller
|
|||||||
RemoteService = remoteService;
|
RemoteService = remoteService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SystemStatusResponse> Get()
|
public async Task<SystemStatusResponse> GetAsync()
|
||||||
{
|
{
|
||||||
SystemStatusResponse response;
|
SystemStatusResponse response;
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ public class SystemStatusController : Controller
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await RemoteService.GetStatus();
|
await RemoteService.GetStatusAsync();
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|
||||||
|
|||||||
363
MoonlightServers.Daemon/Mappers/ServerConfigurationMapper.cs
Normal file
363
MoonlightServers.Daemon/Mappers/ServerConfigurationMapper.cs
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
using Docker.DotNet.Models;
|
||||||
|
using Mono.Unix.Native;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using MoonlightServers.Daemon.Configuration;
|
||||||
|
using MoonlightServers.Daemon.Models.Cache;
|
||||||
|
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Mappers;
|
||||||
|
|
||||||
|
public class ServerConfigurationMapper
|
||||||
|
{
|
||||||
|
private readonly AppConfiguration AppConfiguration;
|
||||||
|
|
||||||
|
public ServerConfigurationMapper(AppConfiguration appConfiguration)
|
||||||
|
{
|
||||||
|
AppConfiguration = appConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerConfiguration FromServerDataResponse(ServerDataResponse response)
|
||||||
|
{
|
||||||
|
return new ServerConfiguration()
|
||||||
|
{
|
||||||
|
Id = response.Id,
|
||||||
|
StartupCommand = response.StartupCommand,
|
||||||
|
Allocations = response.Allocations.Select(y => new ServerConfiguration.AllocationConfiguration()
|
||||||
|
{
|
||||||
|
IpAddress = y.IpAddress,
|
||||||
|
Port = y.Port
|
||||||
|
}).ToArray(),
|
||||||
|
Variables = response.Variables,
|
||||||
|
OnlineDetection = response.OnlineDetection,
|
||||||
|
DockerImage = response.DockerImage,
|
||||||
|
Cpu = response.Cpu,
|
||||||
|
Disk = response.Disk,
|
||||||
|
Memory = response.Memory,
|
||||||
|
StopCommand = response.StopCommand,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public CreateContainerParameters ToRuntimeParameters(
|
||||||
|
ServerConfiguration serverConfiguration,
|
||||||
|
string hostPath,
|
||||||
|
string containerName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var parameters = ToSharedParameters(serverConfiguration);
|
||||||
|
|
||||||
|
#region Security
|
||||||
|
|
||||||
|
parameters.HostConfig.CapDrop = new List<string>()
|
||||||
|
{
|
||||||
|
"setpcap", "mknod", "audit_write", "net_raw", "dac_override",
|
||||||
|
"fowner", "fsetid", "net_bind_service", "sys_chroot", "setfcap"
|
||||||
|
};
|
||||||
|
|
||||||
|
parameters.HostConfig.ReadonlyRootfs = true;
|
||||||
|
parameters.HostConfig.SecurityOpt = new List<string>()
|
||||||
|
{
|
||||||
|
"no-new-privileges"
|
||||||
|
};
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Name
|
||||||
|
|
||||||
|
parameters.Name = containerName;
|
||||||
|
parameters.Hostname = containerName;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Docker Image
|
||||||
|
|
||||||
|
parameters.Image = serverConfiguration.DockerImage;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Working Dir
|
||||||
|
|
||||||
|
parameters.WorkingDir = "/home/container";
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region User
|
||||||
|
|
||||||
|
// TODO: Extract this to an external service with config options and return a userspace user id and a install user id
|
||||||
|
// in order to know which permissions are required in order to run the container with the correct permissions
|
||||||
|
|
||||||
|
var userId = Syscall.getuid();
|
||||||
|
|
||||||
|
if (userId == 0)
|
||||||
|
userId = 998;
|
||||||
|
|
||||||
|
parameters.User = $"{userId}:{userId}";
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (userId == 0)
|
||||||
|
{
|
||||||
|
// We are running as root, so we need to run the container as another user and chown the files when we make changes
|
||||||
|
parameters.User = $"998:998";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We are not running as root, so we start the container as the same user,
|
||||||
|
// as we are not able to chown the container content to a different user
|
||||||
|
parameters.User = $"{userId}:{userId}";
|
||||||
|
}*/
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Mounts
|
||||||
|
|
||||||
|
parameters.HostConfig.Mounts = new List<Mount>();
|
||||||
|
|
||||||
|
parameters.HostConfig.Mounts.Add(new()
|
||||||
|
{
|
||||||
|
Source = hostPath,
|
||||||
|
Target = "/home/container",
|
||||||
|
ReadOnly = false,
|
||||||
|
Type = "bind"
|
||||||
|
});
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Port Bindings
|
||||||
|
|
||||||
|
if (true) // TODO: Add network toggle
|
||||||
|
{
|
||||||
|
parameters.ExposedPorts = new Dictionary<string, EmptyStruct>();
|
||||||
|
parameters.HostConfig.PortBindings = new Dictionary<string, IList<PortBinding>>();
|
||||||
|
|
||||||
|
foreach (var allocation in serverConfiguration.Allocations)
|
||||||
|
{
|
||||||
|
parameters.ExposedPorts.Add($"{allocation.Port}/tcp", new());
|
||||||
|
parameters.ExposedPorts.Add($"{allocation.Port}/udp", new());
|
||||||
|
|
||||||
|
parameters.HostConfig.PortBindings.Add($"{allocation.Port}/tcp", new List<PortBinding>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
HostPort = allocation.Port.ToString(),
|
||||||
|
HostIP = allocation.IpAddress
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
parameters.HostConfig.PortBindings.Add($"{allocation.Port}/udp", new List<PortBinding>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
HostPort = allocation.Port.ToString(),
|
||||||
|
HostIP = allocation.IpAddress
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
// TODO: Implement a way to directly startup a server without using the entrypoint.sh and parsing the startup command here
|
||||||
|
// in the daemon instead of letting it the entrypoint do. iirc pelican wants to do that as well so we need to do that
|
||||||
|
// sooner or later in order to stay compatible to pelican
|
||||||
|
// Possible flag name: LegacyEntrypointMode
|
||||||
|
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CreateContainerParameters ToInstallParameters(
|
||||||
|
ServerConfiguration serverConfiguration,
|
||||||
|
ServerInstallDataResponse installData,
|
||||||
|
string runtimeHostPath,
|
||||||
|
string installationHostPath,
|
||||||
|
string containerName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var parameters = ToSharedParameters(serverConfiguration);
|
||||||
|
|
||||||
|
// - Name
|
||||||
|
parameters.Name = containerName;
|
||||||
|
parameters.Hostname = containerName;
|
||||||
|
|
||||||
|
// - Image
|
||||||
|
parameters.Image = installData.DockerImage;
|
||||||
|
|
||||||
|
// -- Working directory
|
||||||
|
parameters.WorkingDir = "/mnt/server";
|
||||||
|
|
||||||
|
// - User
|
||||||
|
// Note: Some images might not work if we set a user here
|
||||||
|
|
||||||
|
var userId = Syscall.getuid();
|
||||||
|
|
||||||
|
// If we are root, we are able to change owner permissions after the installation
|
||||||
|
// so we run the installation as root, otherwise we need to run it as our current user,
|
||||||
|
// so we are able to access the files created by the installer
|
||||||
|
if (userId == 0)
|
||||||
|
parameters.User = "0:0";
|
||||||
|
else
|
||||||
|
parameters.User = $"{userId}:{userId}";
|
||||||
|
|
||||||
|
// -- Mounts
|
||||||
|
parameters.HostConfig.Mounts = new List<Mount>();
|
||||||
|
|
||||||
|
parameters.HostConfig.Mounts.Add(new()
|
||||||
|
{
|
||||||
|
Source = runtimeHostPath,
|
||||||
|
Target = "/mnt/server",
|
||||||
|
ReadOnly = false,
|
||||||
|
Type = "bind"
|
||||||
|
});
|
||||||
|
|
||||||
|
parameters.HostConfig.Mounts.Add(new()
|
||||||
|
{
|
||||||
|
Source = installationHostPath,
|
||||||
|
Target = "/mnt/install",
|
||||||
|
ReadOnly = false,
|
||||||
|
Type = "bind"
|
||||||
|
});
|
||||||
|
|
||||||
|
parameters.Cmd = [installData.Shell, "/mnt/install/install.sh"];
|
||||||
|
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CreateContainerParameters ToSharedParameters(ServerConfiguration serverConfiguration)
|
||||||
|
{
|
||||||
|
var parameters = new CreateContainerParameters()
|
||||||
|
{
|
||||||
|
HostConfig = new()
|
||||||
|
};
|
||||||
|
|
||||||
|
#region Input, output & error streams and tty
|
||||||
|
|
||||||
|
parameters.Tty = true;
|
||||||
|
parameters.AttachStderr = true;
|
||||||
|
parameters.AttachStdin = true;
|
||||||
|
parameters.AttachStdout = true;
|
||||||
|
parameters.OpenStdin = true;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region CPU
|
||||||
|
|
||||||
|
parameters.HostConfig.CPUQuota = serverConfiguration.Cpu * 1000;
|
||||||
|
parameters.HostConfig.CPUPeriod = 100000;
|
||||||
|
parameters.HostConfig.CPUShares = 1024;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Memory & Swap
|
||||||
|
|
||||||
|
var memoryLimit = serverConfiguration.Memory;
|
||||||
|
|
||||||
|
// The overhead multiplier gives the container a little bit more memory to prevent crashes
|
||||||
|
var memoryOverhead = memoryLimit + (memoryLimit * AppConfiguration.Server.MemoryOverheadMultiplier);
|
||||||
|
|
||||||
|
long swapLimit = -1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
// If swap is enabled globally and not disabled on this server, set swap
|
||||||
|
if (!configuration.Limits.DisableSwap && config.Server.EnableSwap)
|
||||||
|
swapLimit = (long)(memoryOverhead + memoryOverhead * config.Server.SwapMultiplier);
|
||||||
|
co
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Finalize limits by converting and updating the host config
|
||||||
|
parameters.HostConfig.Memory = ByteConverter.FromMegaBytes((long)memoryOverhead, 1000).Bytes;
|
||||||
|
parameters.HostConfig.MemoryReservation = ByteConverter.FromMegaBytes(memoryLimit, 1000).Bytes;
|
||||||
|
parameters.HostConfig.MemorySwap =
|
||||||
|
swapLimit == -1 ? swapLimit : ByteConverter.FromMegaBytes(swapLimit, 1000).Bytes;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Misc Limits
|
||||||
|
|
||||||
|
// -- Other limits
|
||||||
|
parameters.HostConfig.BlkioWeight = 100;
|
||||||
|
//container.HostConfig.PidsLimit = configuration.Limits.PidsLimit;
|
||||||
|
parameters.HostConfig.OomKillDisable = true; //!configuration.Limits.EnableOomKill;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region DNS
|
||||||
|
|
||||||
|
// TODO: Read hosts dns settings?
|
||||||
|
|
||||||
|
parameters.HostConfig.DNS = /*config.Docker.DnsServers.Any() ? config.Docker.DnsServers :*/ new List<string>()
|
||||||
|
{
|
||||||
|
"1.1.1.1",
|
||||||
|
"9.9.9.9"
|
||||||
|
};
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Tmpfs
|
||||||
|
|
||||||
|
parameters.HostConfig.Tmpfs = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "/tmp", $"rw,exec,nosuid,size={AppConfiguration.Server.TmpFsSize}M" }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Logging
|
||||||
|
|
||||||
|
parameters.HostConfig.LogConfig = new()
|
||||||
|
{
|
||||||
|
Type = "json-file", // We need to use this provider, as the GetLogs endpoint needs it
|
||||||
|
Config = new Dictionary<string, string>()
|
||||||
|
};
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Labels
|
||||||
|
|
||||||
|
parameters.Labels = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
parameters.Labels.Add("Software", "Moonlight-Panel");
|
||||||
|
parameters.Labels.Add("ServerId", serverConfiguration.Id.ToString());
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Environment
|
||||||
|
|
||||||
|
parameters.Env = CreateEnvironmentVariables(serverConfiguration);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> CreateEnvironmentVariables(ServerConfiguration serverConfiguration)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
//TODO: Add timezone, add server ip
|
||||||
|
{ "STARTUP", serverConfiguration.StartupCommand },
|
||||||
|
{ "SERVER_MEMORY", serverConfiguration.Memory.ToString() }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (serverConfiguration.Allocations.Length > 0)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < serverConfiguration.Allocations.Length; i++)
|
||||||
|
{
|
||||||
|
var allocation = serverConfiguration.Allocations[i];
|
||||||
|
|
||||||
|
result.Add($"ML_PORT_{i}", allocation.Port.ToString());
|
||||||
|
|
||||||
|
if (i == 0) // TODO: Implement a way to set the default/main allocation
|
||||||
|
{
|
||||||
|
result.Add("SERVER_IP", allocation.IpAddress);
|
||||||
|
result.Add("SERVER_PORT", allocation.Port.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy variables as env vars
|
||||||
|
foreach (var variable in serverConfiguration.Variables)
|
||||||
|
result.Add(variable.Key, variable.Value);
|
||||||
|
|
||||||
|
// Convert to the format of the docker library
|
||||||
|
return result.Select(variable => $"{variable.Key}={variable.Value}").ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,6 @@ public class ServerConfiguration
|
|||||||
public int Cpu { get; set; }
|
public int Cpu { get; set; }
|
||||||
public int Memory { get; set; }
|
public int Memory { get; set; }
|
||||||
public int Disk { get; set; }
|
public int Disk { get; set; }
|
||||||
public int Bandwidth { get; set; }
|
|
||||||
public bool UseVirtualDisk { get; set; }
|
|
||||||
|
|
||||||
// Start, Stop & Status
|
// Start, Stop & Status
|
||||||
public string StartupCommand { get; set; }
|
public string StartupCommand { get; set; }
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public class ServerConsole
|
|||||||
MaxMessagesInCache = maxMessagesInCache;
|
MaxMessagesInCache = maxMessagesInCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task WriteToOutput(string content)
|
public async Task WriteToOutputAsync(string content)
|
||||||
{
|
{
|
||||||
lock (MessageCache)
|
lock (MessageCache)
|
||||||
{
|
{
|
||||||
@@ -32,7 +32,7 @@ public class ServerConsole
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task WriteToInput(string content)
|
public async Task WriteToInputAsync(string content)
|
||||||
{
|
{
|
||||||
if (OnInput != null)
|
if (OnInput != null)
|
||||||
await OnInput.Invoke(content);
|
await OnInput.Invoke(content);
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.9" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
|
||||||
<PackageReference Include="MoonCore" Version="1.8.6" />
|
<PackageReference Include="MoonCore" Version="2.0.1" />
|
||||||
<PackageReference Include="MoonCore.Extended" Version="1.3.3" />
|
<PackageReference Include="MoonCore.Extended" Version="1.4.0" />
|
||||||
<PackageReference Include="MoonCore.Unix" Version="1.0.7" />
|
<PackageReference Include="MoonCore.Unix" Version="1.0.8" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageReference Include="Stateless" Version="5.17.0" />
|
<PackageReference Include="Stateless" Version="5.19.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1"/>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -49,6 +49,21 @@
|
|||||||
<_ContentIncludedByDefault Remove="storage\volumes\2\usercache.json" />
|
<_ContentIncludedByDefault Remove="storage\volumes\2\usercache.json" />
|
||||||
<_ContentIncludedByDefault Remove="storage\volumes\2\version_history.json" />
|
<_ContentIncludedByDefault Remove="storage\volumes\2\version_history.json" />
|
||||||
<_ContentIncludedByDefault Remove="storage\volumes\2\whitelist.json" />
|
<_ContentIncludedByDefault Remove="storage\volumes\2\whitelist.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="volumes\3\banned-ips.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="volumes\3\banned-players.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="volumes\3\ops.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="volumes\3\plugins\spark\config.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="volumes\3\usercache.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="volumes\3\version_history.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="volumes\3\whitelist.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="storage\volumes\69\plugins\spark\config.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="storage\volumes\6\banned-ips.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="storage\volumes\6\banned-players.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="storage\volumes\6\ops.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="storage\volumes\6\plugins\spark\config.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="storage\volumes\6\usercache.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="storage\volumes\6\version_history.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="storage\volumes\6\whitelist.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ using MoonlightServers.Daemon;
|
|||||||
|
|
||||||
var startup = new Startup();
|
var startup = new Startup();
|
||||||
|
|
||||||
await startup.Run(args);
|
await startup.RunAsync(args);
|
||||||
221
MoonlightServers.Daemon/ServerSystem/Docker/DockerConsole.cs
Normal file
221
MoonlightServers.Daemon/ServerSystem/Docker/DockerConsole.cs
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Docker.DotNet;
|
||||||
|
using MoonCore.Events;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Docker;
|
||||||
|
|
||||||
|
public class DockerConsole : IConsole
|
||||||
|
{
|
||||||
|
private readonly EventSource<string> StdOutEventSource = new();
|
||||||
|
private readonly ConcurrentList<string> StdOutCache = new();
|
||||||
|
private readonly DockerClient DockerClient;
|
||||||
|
private readonly ServerContext Context;
|
||||||
|
private readonly ILogger Logger;
|
||||||
|
|
||||||
|
private MultiplexedStream? CurrentStream;
|
||||||
|
private CancellationTokenSource Cts = new();
|
||||||
|
|
||||||
|
public DockerConsole(DockerClient dockerClient, ServerContext context)
|
||||||
|
{
|
||||||
|
DockerClient = dockerClient;
|
||||||
|
Context = context;
|
||||||
|
Logger = Context.Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task WriteStdInAsync(string content)
|
||||||
|
{
|
||||||
|
if (CurrentStream == null)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Unable to write to stdin as no stream is connected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var contextBuffer = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
|
await CurrentStream.WriteAsync(contextBuffer, 0, contextBuffer.Length, Cts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteStdOutAsync(string content)
|
||||||
|
{
|
||||||
|
// Add output cache
|
||||||
|
if (StdOutCache.Count > 250) // TODO: Config
|
||||||
|
StdOutCache.RemoveRange(0, 100);
|
||||||
|
|
||||||
|
StdOutCache.Add(content);
|
||||||
|
|
||||||
|
// Fire event
|
||||||
|
await StdOutEventSource.InvokeAsync(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AttachRuntimeAsync()
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||||
|
|
||||||
|
await AttachToContainerAsync(containerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AttachInstallationAsync()
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.InstallationNameTemplate, Context.Configuration.Id);
|
||||||
|
|
||||||
|
await AttachToContainerAsync(containerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AttachToContainerAsync(string containerName)
|
||||||
|
{
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
// Cancels previous active read task if it exists
|
||||||
|
if (!Cts.IsCancellationRequested)
|
||||||
|
await Cts.CancelAsync();
|
||||||
|
|
||||||
|
// Update the current cancellation token
|
||||||
|
Cts = cts;
|
||||||
|
|
||||||
|
// Start reading task
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// This loop is here to reconnect to the stream when connection is lost.
|
||||||
|
// This can occur when docker restarts for example
|
||||||
|
|
||||||
|
while (!cts.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
MultiplexedStream? innerStream = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.LogTrace("Attaching");
|
||||||
|
|
||||||
|
innerStream = await DockerClient.Containers.AttachContainerAsync(
|
||||||
|
containerName,
|
||||||
|
true,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Stderr = true,
|
||||||
|
Stdin = true,
|
||||||
|
Stdout = true,
|
||||||
|
Stream = true
|
||||||
|
},
|
||||||
|
cts.Token
|
||||||
|
);
|
||||||
|
|
||||||
|
CurrentStream = innerStream;
|
||||||
|
|
||||||
|
var buffer = new byte[1024];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Read while server tasks are not canceled
|
||||||
|
while (!cts.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var readResult = await innerStream.ReadOutputAsync(
|
||||||
|
buffer,
|
||||||
|
0,
|
||||||
|
buffer.Length,
|
||||||
|
cts.Token
|
||||||
|
);
|
||||||
|
|
||||||
|
if (readResult.EOF)
|
||||||
|
await cts.CancelAsync();
|
||||||
|
|
||||||
|
var decodedText = Encoding.UTF8.GetString(buffer, 0, readResult.Count);
|
||||||
|
|
||||||
|
await WriteStdOutAsync(decodedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogTrace("Read loop exited");
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(e, "An unhandled error occured while reading from container stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
// Container got removed. Stop the reconnect loop
|
||||||
|
|
||||||
|
Logger.LogDebug("Container '{name}' got removed. Stopping reconnect stream for console", containerName);
|
||||||
|
await cts.CancelAsync();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(e, "An error occured while attaching to container");
|
||||||
|
}
|
||||||
|
|
||||||
|
innerStream?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogDebug("Disconnected from container stream");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task FetchRuntimeAsync()
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||||
|
|
||||||
|
await FetchFromContainerAsync(containerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task FetchInstallationAsync()
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.InstallationNameTemplate, Context.Configuration.Id);
|
||||||
|
|
||||||
|
await FetchFromContainerAsync(containerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FetchFromContainerAsync(string containerName)
|
||||||
|
{
|
||||||
|
var logStream = await DockerClient.Containers.GetContainerLogsAsync(containerName, true, new()
|
||||||
|
{
|
||||||
|
Follow = false,
|
||||||
|
ShowStderr = true,
|
||||||
|
ShowStdout = true
|
||||||
|
});
|
||||||
|
|
||||||
|
var combinedOutput = await logStream.ReadOutputToEndAsync(Cts.Token);
|
||||||
|
var contentToAdd = combinedOutput.stdout + combinedOutput.stderr;
|
||||||
|
|
||||||
|
await WriteStdOutAsync(contentToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ClearCacheAsync()
|
||||||
|
{
|
||||||
|
StdOutCache.Clear();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<string>> GetCacheAsync()
|
||||||
|
{
|
||||||
|
return Task.FromResult<IEnumerable<string>>(StdOutCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IAsyncDisposable> SubscribeStdOutAsync(Func<string, ValueTask> callback)
|
||||||
|
=> await StdOutEventSource.SubscribeAsync(callback);
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (!Cts.IsCancellationRequested)
|
||||||
|
await Cts.CancelAsync();
|
||||||
|
|
||||||
|
if (CurrentStream != null)
|
||||||
|
CurrentStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Docker;
|
||||||
|
|
||||||
|
public static class DockerConstants
|
||||||
|
{
|
||||||
|
public const string RuntimeNameTemplate = "moonlight-runtime-{0}";
|
||||||
|
public const string InstallationNameTemplate = "moonlight-installation-{0}";
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
using Docker.DotNet;
|
||||||
|
using Docker.DotNet.Models;
|
||||||
|
using MoonCore.Events;
|
||||||
|
using MoonlightServers.Daemon.Mappers;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
using MoonlightServers.Daemon.Services;
|
||||||
|
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Docker;
|
||||||
|
|
||||||
|
public class DockerInstallation : IInstallation
|
||||||
|
{
|
||||||
|
private readonly DockerEventService DockerEventService;
|
||||||
|
private readonly ServerConfigurationMapper Mapper;
|
||||||
|
private readonly DockerImageService ImageService;
|
||||||
|
private readonly ServerContext ServerContext;
|
||||||
|
private readonly DockerClient DockerClient;
|
||||||
|
private IReporter Reporter => ServerContext.Server.Reporter;
|
||||||
|
|
||||||
|
private readonly EventSource<int> ExitEventSource = new();
|
||||||
|
|
||||||
|
private IAsyncDisposable ContainerEventSubscription;
|
||||||
|
private string ContainerId;
|
||||||
|
|
||||||
|
public DockerInstallation(
|
||||||
|
DockerClient dockerClient,
|
||||||
|
ServerContext serverContext,
|
||||||
|
ServerConfigurationMapper mapper,
|
||||||
|
DockerImageService imageService,
|
||||||
|
DockerEventService dockerEventService
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DockerClient = dockerClient;
|
||||||
|
ServerContext = serverContext;
|
||||||
|
Mapper = mapper;
|
||||||
|
ImageService = imageService;
|
||||||
|
DockerEventService = dockerEventService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
ContainerEventSubscription = await DockerEventService.SubscribeContainerAsync(OnContainerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask OnContainerEvent(Message message)
|
||||||
|
{
|
||||||
|
// Only handle events for our own container
|
||||||
|
if (message.ID != ContainerId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Only handle die events
|
||||||
|
if (message.Action != "die")
|
||||||
|
return;
|
||||||
|
|
||||||
|
int exitCode;
|
||||||
|
|
||||||
|
if (message.Actor.Attributes.TryGetValue("exitCode", out var exitCodeStr))
|
||||||
|
{
|
||||||
|
if (!int.TryParse(exitCodeStr, out exitCode))
|
||||||
|
exitCode = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
exitCode = 0;
|
||||||
|
|
||||||
|
|
||||||
|
await ExitEventSource.InvokeAsync(exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CheckExistsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.InstallationNameTemplate, ServerContext.Configuration.Id);
|
||||||
|
|
||||||
|
await DockerClient.Containers.InspectContainerAsync(
|
||||||
|
containerName
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateAsync(
|
||||||
|
string runtimePath,
|
||||||
|
string hostPath,
|
||||||
|
ServerInstallDataResponse data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.InstallationNameTemplate, ServerContext.Configuration.Id);
|
||||||
|
|
||||||
|
var parameters = Mapper.ToInstallParameters(
|
||||||
|
ServerContext.Configuration,
|
||||||
|
data,
|
||||||
|
runtimePath,
|
||||||
|
hostPath,
|
||||||
|
containerName
|
||||||
|
);
|
||||||
|
|
||||||
|
// Docker image
|
||||||
|
await Reporter.StatusAsync("Downloading docker image");
|
||||||
|
|
||||||
|
await ImageService.DownloadAsync(data.DockerImage, async status => { await Reporter.StatusAsync(status); });
|
||||||
|
|
||||||
|
await Reporter.StatusAsync("Downloaded docker image");
|
||||||
|
|
||||||
|
// Write install script to install fs
|
||||||
|
|
||||||
|
await File.WriteAllTextAsync(
|
||||||
|
Path.Combine(hostPath, "install.sh"),
|
||||||
|
data.Script
|
||||||
|
);
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
var response = await DockerClient.Containers.CreateContainerAsync(parameters);
|
||||||
|
ContainerId = response.ID;
|
||||||
|
|
||||||
|
await Reporter.StatusAsync("Created container");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartAsync()
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.InstallationNameTemplate, ServerContext.Configuration.Id);
|
||||||
|
|
||||||
|
await DockerClient.Containers.StartContainerAsync(containerName, new());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task KillAsync()
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.InstallationNameTemplate, ServerContext.Configuration.Id);
|
||||||
|
|
||||||
|
await DockerClient.Containers.KillContainerAsync(containerName, new());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DestroyAsync()
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.InstallationNameTemplate, ServerContext.Configuration.Id);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var container = await DockerClient.Containers.InspectContainerAsync(containerName);
|
||||||
|
|
||||||
|
if (container.State.Running)
|
||||||
|
await DockerClient.Containers.KillContainerAsync(containerName, new());
|
||||||
|
|
||||||
|
await DockerClient.Containers.RemoveContainerAsync(containerName, new()
|
||||||
|
{
|
||||||
|
Force = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IAsyncDisposable> SubscribeExitedAsync(Func<int, ValueTask> callback)
|
||||||
|
=> await ExitEventSource.SubscribeAsync(callback);
|
||||||
|
|
||||||
|
public async Task RestoreAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.InstallationNameTemplate, ServerContext.Configuration.Id);
|
||||||
|
|
||||||
|
var container = await DockerClient.Containers.InspectContainerAsync(containerName);
|
||||||
|
ContainerId = container.ID;
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await ContainerEventSubscription.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using Docker.DotNet;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Docker;
|
||||||
|
|
||||||
|
public class DockerRestorer : IRestorer
|
||||||
|
{
|
||||||
|
private readonly DockerClient DockerClient;
|
||||||
|
private readonly ServerContext Context;
|
||||||
|
|
||||||
|
public DockerRestorer(DockerClient dockerClient, ServerContext context)
|
||||||
|
{
|
||||||
|
DockerClient = dockerClient;
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task<bool> HandleRuntimeAsync()
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var container = await DockerClient.Containers.InspectContainerAsync(
|
||||||
|
containerName
|
||||||
|
);
|
||||||
|
|
||||||
|
return container.State.Running;
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> HandleInstallationAsync()
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.InstallationNameTemplate, Context.Configuration.Id);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var container = await DockerClient.Containers.InspectContainerAsync(
|
||||||
|
containerName
|
||||||
|
);
|
||||||
|
|
||||||
|
return container.State.Running;
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync()
|
||||||
|
=> ValueTask.CompletedTask;
|
||||||
|
}
|
||||||
177
MoonlightServers.Daemon/ServerSystem/Docker/DockerRuntime.cs
Normal file
177
MoonlightServers.Daemon/ServerSystem/Docker/DockerRuntime.cs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
using Docker.DotNet;
|
||||||
|
using Docker.DotNet.Models;
|
||||||
|
using MoonCore.Events;
|
||||||
|
using MoonlightServers.Daemon.Mappers;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
using MoonlightServers.Daemon.Services;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Docker;
|
||||||
|
|
||||||
|
public class DockerRuntime : IRuntime
|
||||||
|
{
|
||||||
|
private readonly DockerClient DockerClient;
|
||||||
|
private readonly ServerContext Context;
|
||||||
|
private readonly ServerConfigurationMapper Mapper;
|
||||||
|
private readonly DockerEventService DockerEventService;
|
||||||
|
private readonly DockerImageService ImageService;
|
||||||
|
private readonly EventSource<int> ExitEventSource = new();
|
||||||
|
|
||||||
|
private IReporter Reporter => Context.Server.Reporter;
|
||||||
|
private IAsyncDisposable ContainerEventSubscription;
|
||||||
|
private string ContainerId;
|
||||||
|
|
||||||
|
public DockerRuntime(
|
||||||
|
DockerClient dockerClient,
|
||||||
|
ServerContext context,
|
||||||
|
ServerConfigurationMapper mapper,
|
||||||
|
DockerEventService dockerEventService,
|
||||||
|
DockerImageService imageService
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DockerClient = dockerClient;
|
||||||
|
Context = context;
|
||||||
|
Mapper = mapper;
|
||||||
|
DockerEventService = dockerEventService;
|
||||||
|
ImageService = imageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
ContainerEventSubscription = await DockerEventService.SubscribeContainerAsync(OnContainerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask OnContainerEvent(Message message)
|
||||||
|
{
|
||||||
|
// Only handle events for our own container
|
||||||
|
if (message.ID != ContainerId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Only handle die events
|
||||||
|
if (message.Action != "die")
|
||||||
|
return;
|
||||||
|
|
||||||
|
int exitCode;
|
||||||
|
|
||||||
|
if (message.Actor.Attributes.TryGetValue("exitCode", out var exitCodeStr))
|
||||||
|
{
|
||||||
|
if (!int.TryParse(exitCodeStr, out exitCode))
|
||||||
|
exitCode = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
exitCode = 0;
|
||||||
|
|
||||||
|
|
||||||
|
await ExitEventSource.InvokeAsync(exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CheckExistsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||||
|
|
||||||
|
await DockerClient.Containers.InspectContainerAsync(
|
||||||
|
containerName
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateAsync(string path)
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||||
|
|
||||||
|
var parameters = Mapper.ToRuntimeParameters(
|
||||||
|
Context.Configuration,
|
||||||
|
path,
|
||||||
|
containerName
|
||||||
|
);
|
||||||
|
|
||||||
|
// Docker image
|
||||||
|
await Reporter.StatusAsync("Downloading docker image");
|
||||||
|
|
||||||
|
await ImageService.DownloadAsync(
|
||||||
|
Context.Configuration.DockerImage,
|
||||||
|
async status => { await Reporter.StatusAsync(status); }
|
||||||
|
);
|
||||||
|
|
||||||
|
await Reporter.StatusAsync("Downloaded docker image");
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
var response = await DockerClient.Containers.CreateContainerAsync(parameters);
|
||||||
|
ContainerId = response.ID;
|
||||||
|
|
||||||
|
await Reporter.StatusAsync("Created container");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartAsync()
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||||
|
|
||||||
|
await DockerClient.Containers.StartContainerAsync(containerName, new());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task UpdateAsync()
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task KillAsync()
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||||
|
|
||||||
|
await DockerClient.Containers.KillContainerAsync(containerName, new());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DestroyAsync()
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var container = await DockerClient.Containers.InspectContainerAsync(containerName);
|
||||||
|
|
||||||
|
if (container.State.Running)
|
||||||
|
await DockerClient.Containers.KillContainerAsync(containerName, new());
|
||||||
|
|
||||||
|
await DockerClient.Containers.RemoveContainerAsync(containerName, new()
|
||||||
|
{
|
||||||
|
Force = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IAsyncDisposable> SubscribeExitedAsync(Func<int, ValueTask> callback)
|
||||||
|
=> await ExitEventSource.SubscribeAsync(callback);
|
||||||
|
|
||||||
|
public async Task RestoreAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
|
||||||
|
|
||||||
|
var container = await DockerClient.Containers.InspectContainerAsync(containerName);
|
||||||
|
ContainerId = container.ID;
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await ContainerEventSubscription.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Docker;
|
||||||
|
|
||||||
|
public class DockerStatistics : IStatistics
|
||||||
|
{
|
||||||
|
public Task InitializeAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task AttachRuntimeAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task AttachInstallationAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task ClearCacheAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task<IEnumerable<StatisticsData>> GetCacheAsync()
|
||||||
|
=> Task.FromResult<IEnumerable<StatisticsData>>([]);
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync()
|
||||||
|
=> ValueTask.CompletedTask;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace MoonlightServers.Daemon.ServerSystem;
|
namespace MoonlightServers.Daemon.ServerSystem.Enums;
|
||||||
|
|
||||||
public enum ServerState
|
public enum ServerState
|
||||||
{
|
{
|
||||||
@@ -6,5 +6,6 @@ public enum ServerState
|
|||||||
Starting = 1,
|
Starting = 1,
|
||||||
Online = 2,
|
Online = 2,
|
||||||
Stopping = 3,
|
Stopping = 3,
|
||||||
Installing = 4
|
Installing = 4,
|
||||||
|
Locked = 5
|
||||||
}
|
}
|
||||||
12
MoonlightServers.Daemon/ServerSystem/Enums/ServerTrigger.cs
Normal file
12
MoonlightServers.Daemon/ServerSystem/Enums/ServerTrigger.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Enums;
|
||||||
|
|
||||||
|
public enum ServerTrigger
|
||||||
|
{
|
||||||
|
Start = 0,
|
||||||
|
Stop = 1,
|
||||||
|
Kill = 2,
|
||||||
|
DetectOnline = 3,
|
||||||
|
Install = 4,
|
||||||
|
Fail = 5,
|
||||||
|
Exited = 6
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.FileSystems;
|
||||||
|
|
||||||
|
public class RawInstallationFs : IFileSystem
|
||||||
|
{
|
||||||
|
private readonly string BaseDirectory;
|
||||||
|
|
||||||
|
public RawInstallationFs(ServerContext context)
|
||||||
|
{
|
||||||
|
BaseDirectory = Path.Combine(
|
||||||
|
Directory.GetCurrentDirectory(),
|
||||||
|
"storage",
|
||||||
|
"install",
|
||||||
|
context.Configuration.Id.ToString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task<string> GetPathAsync()
|
||||||
|
=> Task.FromResult(BaseDirectory);
|
||||||
|
|
||||||
|
public Task<bool> CheckExistsAsync()
|
||||||
|
{
|
||||||
|
var exists = Directory.Exists(BaseDirectory);
|
||||||
|
return Task.FromResult(exists);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> CheckMountedAsync()
|
||||||
|
=> Task.FromResult(true);
|
||||||
|
|
||||||
|
public Task CreateAsync()
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(BaseDirectory);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task PerformChecksAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task MountAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task UnmountAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task DestroyAsync()
|
||||||
|
{
|
||||||
|
Directory.Delete(BaseDirectory, true);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync()
|
||||||
|
=> ValueTask.CompletedTask;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.FileSystems;
|
||||||
|
|
||||||
|
public class RawRuntimeFs : IFileSystem
|
||||||
|
{
|
||||||
|
private readonly string BaseDirectory;
|
||||||
|
|
||||||
|
public RawRuntimeFs(ServerContext context)
|
||||||
|
{
|
||||||
|
BaseDirectory = Path.Combine(
|
||||||
|
Directory.GetCurrentDirectory(),
|
||||||
|
"storage",
|
||||||
|
"volumes",
|
||||||
|
context.Configuration.Id.ToString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task<string> GetPathAsync()
|
||||||
|
=> Task.FromResult(BaseDirectory);
|
||||||
|
|
||||||
|
public Task<bool> CheckExistsAsync()
|
||||||
|
{
|
||||||
|
var exists = Directory.Exists(BaseDirectory);
|
||||||
|
return Task.FromResult(exists);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> CheckMountedAsync()
|
||||||
|
=> Task.FromResult(true);
|
||||||
|
|
||||||
|
public Task CreateAsync()
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(BaseDirectory);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task PerformChecksAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task MountAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task UnmountAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task DestroyAsync()
|
||||||
|
{
|
||||||
|
Directory.Delete(BaseDirectory, true);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync()
|
||||||
|
=> ValueTask.CompletedTask;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using MoonlightServers.Daemon.ServerSystem.Enums;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
using Stateless;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Handlers;
|
||||||
|
|
||||||
|
public class DebugHandler : IServerStateHandler
|
||||||
|
{
|
||||||
|
private readonly ServerContext Context;
|
||||||
|
private IAsyncDisposable? StdOutSubscription;
|
||||||
|
|
||||||
|
public DebugHandler(ServerContext context)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync(StateMachine<ServerState, ServerTrigger>.Transition transition)
|
||||||
|
{
|
||||||
|
if(StdOutSubscription != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
StdOutSubscription = await Context.Server.Console.SubscribeStdOutAsync(line =>
|
||||||
|
{
|
||||||
|
Console.WriteLine($"STD OUT: {line}");
|
||||||
|
return ValueTask.CompletedTask;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (StdOutSubscription != null)
|
||||||
|
await StdOutSubscription.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
using MoonlightServers.Daemon.ServerSystem.Enums;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||||
|
using Stateless;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Handlers;
|
||||||
|
|
||||||
|
public class InstallationHandler : IServerStateHandler
|
||||||
|
{
|
||||||
|
private readonly ServerContext Context;
|
||||||
|
private Server Server => Context.Server;
|
||||||
|
|
||||||
|
private IAsyncDisposable? ExitSubscription;
|
||||||
|
|
||||||
|
public InstallationHandler(ServerContext context)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync(StateMachine<ServerState, ServerTrigger>.Transition transition)
|
||||||
|
{
|
||||||
|
if (transition is
|
||||||
|
{ Source: ServerState.Offline, Destination: ServerState.Installing, Trigger: ServerTrigger.Install })
|
||||||
|
{
|
||||||
|
await StartAsync();
|
||||||
|
}
|
||||||
|
else if (transition is
|
||||||
|
{ Source: ServerState.Installing, Destination: ServerState.Offline, Trigger: ServerTrigger.Exited })
|
||||||
|
{
|
||||||
|
await CompleteAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartAsync()
|
||||||
|
{
|
||||||
|
// Plan:
|
||||||
|
// 1. Fetch latest configuration
|
||||||
|
// 2. Check if both file systems exists
|
||||||
|
// 3. Check if both file systems are mounted
|
||||||
|
// 4. Run file system checks
|
||||||
|
// 5. Create installation container
|
||||||
|
// 6. Attach console
|
||||||
|
// 7. Start installation container
|
||||||
|
|
||||||
|
// 1. Fetch latest configuration
|
||||||
|
var installData = new ServerInstallDataResponse()
|
||||||
|
{
|
||||||
|
Script = await File.ReadAllTextAsync(Path.Combine("storage", "install.sh")),
|
||||||
|
Shell = "/bin/ash",
|
||||||
|
DockerImage = "ghcr.io/parkervcp/installers:alpine"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Check if file system exists
|
||||||
|
if (!await Server.RuntimeFileSystem.CheckExistsAsync())
|
||||||
|
await Server.RuntimeFileSystem.CreateAsync();
|
||||||
|
|
||||||
|
if (!await Server.InstallationFileSystem.CheckExistsAsync())
|
||||||
|
await Server.InstallationFileSystem.CreateAsync();
|
||||||
|
|
||||||
|
// 3. Check if both file systems are mounted
|
||||||
|
if (!await Server.RuntimeFileSystem.CheckMountedAsync())
|
||||||
|
await Server.RuntimeFileSystem.MountAsync();
|
||||||
|
|
||||||
|
if (!await Server.InstallationFileSystem.CheckMountedAsync())
|
||||||
|
await Server.InstallationFileSystem.MountAsync();
|
||||||
|
|
||||||
|
// 4. Run file system checks
|
||||||
|
await Server.RuntimeFileSystem.PerformChecksAsync();
|
||||||
|
await Server.InstallationFileSystem.PerformChecksAsync();
|
||||||
|
|
||||||
|
// 5. Create installation
|
||||||
|
|
||||||
|
var runtimePath = await Server.RuntimeFileSystem.GetPathAsync();
|
||||||
|
var installationPath = await Server.InstallationFileSystem.GetPathAsync();
|
||||||
|
|
||||||
|
if (await Server.Installation.CheckExistsAsync())
|
||||||
|
await Server.Installation.DestroyAsync();
|
||||||
|
|
||||||
|
await Server.Installation.CreateAsync(runtimePath, installationPath, installData);
|
||||||
|
|
||||||
|
if (ExitSubscription == null)
|
||||||
|
ExitSubscription = await Server.Installation.SubscribeExitedAsync(OnInstallationExited);
|
||||||
|
|
||||||
|
// 6. Attach console
|
||||||
|
|
||||||
|
await Server.Console.AttachInstallationAsync();
|
||||||
|
|
||||||
|
// 7. Start installation container
|
||||||
|
await Server.Installation.StartAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask OnInstallationExited(int exitCode)
|
||||||
|
{
|
||||||
|
// TODO: Notify the crash handler component of the exit code
|
||||||
|
|
||||||
|
await Server.StateMachine.FireAsync(ServerTrigger.Exited);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CompleteAsync()
|
||||||
|
{
|
||||||
|
// Plan:
|
||||||
|
// 1. Handle possible crash
|
||||||
|
// 2. Remove installation container
|
||||||
|
// 3. Remove installation file system
|
||||||
|
|
||||||
|
// 1. Handle possible crash
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// 2. Remove installation container
|
||||||
|
await Server.Installation.DestroyAsync();
|
||||||
|
|
||||||
|
// 3. Remove installation file system
|
||||||
|
await Server.InstallationFileSystem.UnmountAsync();
|
||||||
|
await Server.InstallationFileSystem.DestroyAsync();
|
||||||
|
|
||||||
|
Context.Logger.LogDebug("Completed installation");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (ExitSubscription != null)
|
||||||
|
await ExitSubscription.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using MoonlightServers.Daemon.ServerSystem.Enums;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
using Stateless;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Handlers;
|
||||||
|
|
||||||
|
public class OnlineDetectionHandler : IServerStateHandler
|
||||||
|
{
|
||||||
|
private readonly ServerContext Context;
|
||||||
|
private IOnlineDetector OnlineDetector => Context.Server.OnlineDetector;
|
||||||
|
private ILogger Logger => Context.Logger;
|
||||||
|
|
||||||
|
private IAsyncDisposable? ConsoleSubscription;
|
||||||
|
private bool IsActive = false;
|
||||||
|
|
||||||
|
public OnlineDetectionHandler(ServerContext context)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync(StateMachine<ServerState, ServerTrigger>.Transition transition)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
transition is
|
||||||
|
{ Source: ServerState.Offline, Destination: ServerState.Starting, Trigger: ServerTrigger.Start } && !IsActive
|
||||||
|
)
|
||||||
|
{
|
||||||
|
await StartAsync();
|
||||||
|
}
|
||||||
|
else if (transition is { Source: not ServerState.Installing, Destination: ServerState.Offline } && IsActive)
|
||||||
|
{
|
||||||
|
await StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartAsync()
|
||||||
|
{
|
||||||
|
IsActive = true;
|
||||||
|
|
||||||
|
await OnlineDetector.CreateAsync();
|
||||||
|
|
||||||
|
ConsoleSubscription = await Context.Server.Console.SubscribeStdOutAsync(OnHandleOutput);
|
||||||
|
|
||||||
|
Logger.LogTrace("Created online detector. Created console subscription");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask OnHandleOutput(string line)
|
||||||
|
{
|
||||||
|
if(!IsActive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(!await OnlineDetector.HandleOutputAsync(line))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(!Context.Server.StateMachine.CanFire(ServerTrigger.DetectOnline))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Logger.LogTrace("Detected server as online. Destroying online detector");
|
||||||
|
|
||||||
|
await Context.Server.StateMachine.FireAsync(ServerTrigger.DetectOnline);
|
||||||
|
await StopAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StopAsync()
|
||||||
|
{
|
||||||
|
IsActive = false;
|
||||||
|
|
||||||
|
if (ConsoleSubscription != null)
|
||||||
|
{
|
||||||
|
await ConsoleSubscription.DisposeAsync();
|
||||||
|
ConsoleSubscription = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await OnlineDetector.DestroyAsync();
|
||||||
|
|
||||||
|
Logger.LogTrace("Destroyed online detector. Revoked console subscription");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (ConsoleSubscription != null)
|
||||||
|
await ConsoleSubscription.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using MoonlightServers.Daemon.ServerSystem.Enums;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
using Stateless;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Handlers;
|
||||||
|
|
||||||
|
public class ShutdownHandler : IServerStateHandler
|
||||||
|
{
|
||||||
|
private readonly ServerContext ServerContext;
|
||||||
|
|
||||||
|
public ShutdownHandler(ServerContext serverContext)
|
||||||
|
{
|
||||||
|
ServerContext = serverContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync(StateMachine<ServerState, ServerTrigger>.Transition transition)
|
||||||
|
{
|
||||||
|
// Filter (we only want to handle exists from the runtime, so we filter out the installing state)
|
||||||
|
if (transition is not
|
||||||
|
{
|
||||||
|
Destination: ServerState.Offline,
|
||||||
|
Source: not ServerState.Installing,
|
||||||
|
Trigger: ServerTrigger.Exited // We don't want to handle the fail event here
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Plan:
|
||||||
|
// 1. Handle possible crash
|
||||||
|
// 2. Remove runtime
|
||||||
|
|
||||||
|
// 1. Handle possible crash
|
||||||
|
// TODO: Handle crash here
|
||||||
|
|
||||||
|
// 2. Remove runtime
|
||||||
|
|
||||||
|
await ServerContext.Server.Runtime.DestroyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync()
|
||||||
|
=> ValueTask.CompletedTask;
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using MoonlightServers.Daemon.ServerSystem.Enums;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
using Stateless;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Handlers;
|
||||||
|
|
||||||
|
public class StartupHandler : IServerStateHandler
|
||||||
|
{
|
||||||
|
private IAsyncDisposable? ExitSubscription;
|
||||||
|
|
||||||
|
private readonly ServerContext Context;
|
||||||
|
private Server Server => Context.Server;
|
||||||
|
|
||||||
|
public StartupHandler(ServerContext context)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync(StateMachine<ServerState, ServerTrigger>.Transition transition)
|
||||||
|
{
|
||||||
|
// Filter
|
||||||
|
if (transition is not {Source: ServerState.Offline, Destination: ServerState.Starting, Trigger: ServerTrigger.Start})
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Plan:
|
||||||
|
// 1. Fetch latest configuration
|
||||||
|
// 2. Check if file system exists
|
||||||
|
// 3. Check if file system is mounted
|
||||||
|
// 4. Run file system checks
|
||||||
|
// 5. Create runtime
|
||||||
|
// 6. Attach console
|
||||||
|
// 7. Start runtime
|
||||||
|
|
||||||
|
// 1. Fetch latest configuration
|
||||||
|
// TODO
|
||||||
|
// Consider moving it out of the startup handler, as other handlers might need
|
||||||
|
// the updated config as well or add sorting into the handler registration to ensure they are executing in the correct order.
|
||||||
|
// Sort when building server, not when executing handlers
|
||||||
|
|
||||||
|
// 2. Check if file system exists
|
||||||
|
if (!await Server.RuntimeFileSystem.CheckExistsAsync())
|
||||||
|
await Server.RuntimeFileSystem.CreateAsync();
|
||||||
|
|
||||||
|
// 3. Check if file system is mounted
|
||||||
|
if (!await Server.RuntimeFileSystem.CheckMountedAsync())
|
||||||
|
await Server.RuntimeFileSystem.CheckMountedAsync();
|
||||||
|
|
||||||
|
// 4. Run file system checks
|
||||||
|
await Server.RuntimeFileSystem.PerformChecksAsync();
|
||||||
|
|
||||||
|
// 5. Create runtime
|
||||||
|
var hostPath = await Server.RuntimeFileSystem.GetPathAsync();
|
||||||
|
|
||||||
|
if (await Server.Runtime.CheckExistsAsync())
|
||||||
|
await Server.Runtime.DestroyAsync();
|
||||||
|
|
||||||
|
await Server.Runtime.CreateAsync(hostPath);
|
||||||
|
|
||||||
|
if (ExitSubscription == null)
|
||||||
|
ExitSubscription = await Server.Runtime.SubscribeExitedAsync(OnRuntimeExited);
|
||||||
|
|
||||||
|
// 6. Attach console
|
||||||
|
|
||||||
|
await Server.Console.AttachRuntimeAsync();
|
||||||
|
|
||||||
|
// 7. Start runtime
|
||||||
|
|
||||||
|
await Server.Runtime.StartAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask OnRuntimeExited(int exitCode)
|
||||||
|
{
|
||||||
|
// TODO: Notify the crash handler component of the exit code
|
||||||
|
|
||||||
|
await Server.StateMachine.FireAsync(ServerTrigger.Exited);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (ExitSubscription != null)
|
||||||
|
await ExitSubscription.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using MoonlightServers.Daemon.Http.Hubs;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Implementations;
|
||||||
|
|
||||||
|
public class ConsoleSignalRComponent : IServerComponent
|
||||||
|
{
|
||||||
|
private readonly IHubContext<ServerWebSocketHub> Hub;
|
||||||
|
private readonly ServerContext Context;
|
||||||
|
|
||||||
|
private IAsyncDisposable? StdOutSubscription;
|
||||||
|
private string HubGroup;
|
||||||
|
|
||||||
|
public ConsoleSignalRComponent(IHubContext<ServerWebSocketHub> hub, ServerContext context)
|
||||||
|
{
|
||||||
|
Hub = hub;
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
HubGroup = Context.Configuration.Id.ToString();
|
||||||
|
|
||||||
|
StdOutSubscription = await Context.Server.Console.SubscribeStdOutAsync(OnStdOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask OnStdOut(string output)
|
||||||
|
{
|
||||||
|
await Hub.Clients.Group(HubGroup).SendAsync("ConsoleOutput", output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (StdOutSubscription != null)
|
||||||
|
await StdOutSubscription.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Implementations;
|
||||||
|
|
||||||
|
public class RegexOnlineDetector : IOnlineDetector
|
||||||
|
{
|
||||||
|
private readonly ServerContext Context;
|
||||||
|
|
||||||
|
private Regex? Expression;
|
||||||
|
|
||||||
|
public RegexOnlineDetector(ServerContext context)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task CreateAsync()
|
||||||
|
{
|
||||||
|
if(string.IsNullOrEmpty(Context.Configuration.OnlineDetection))
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
Expression = new Regex(Context.Configuration.OnlineDetection, RegexOptions.Compiled);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> HandleOutputAsync(string line)
|
||||||
|
{
|
||||||
|
if (Expression == null)
|
||||||
|
return Task.FromResult(false);
|
||||||
|
|
||||||
|
var result = Expression.Matches(line).Count > 0;
|
||||||
|
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DestroyAsync()
|
||||||
|
{
|
||||||
|
Expression = null;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
Expression = null;
|
||||||
|
return ValueTask.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Implementations;
|
||||||
|
|
||||||
|
public class ServerReporter : IReporter
|
||||||
|
{
|
||||||
|
private readonly ServerContext Context;
|
||||||
|
|
||||||
|
private const string StatusTemplate =
|
||||||
|
"\x1b[1;38;2;200;90;200mM\x1b[1;38;2;204;110;230mo\x1b[1;38;2;170;130;245mo\x1b[1;38;2;140;150;255mn\x1b[1;38;2;110;180;255ml\x1b[1;38;2;100;200;255mi\x1b[1;38;2;100;220;255mg\x1b[1;38;2;120;235;255mh\x1b[1;38;2;140;250;255mt\x1b[0m \x1b[3;38;2;200;200;200m{0}\x1b[0m\n\r";
|
||||||
|
|
||||||
|
private const string ErrorTemplate =
|
||||||
|
"\x1b[1;38;2;200;90;200mM\x1b[1;38;2;204;110;230mo\x1b[1;38;2;170;130;245mo\x1b[1;38;2;140;150;255mn\x1b[1;38;2;110;180;255ml\x1b[1;38;2;100;200;255mi\x1b[1;38;2;100;220;255mg\x1b[1;38;2;120;235;255mh\x1b[1;38;2;140;250;255mt\x1b[0m \x1b[1;38;2;255;0;0m{0}\x1b[0m\n\r";
|
||||||
|
|
||||||
|
public ServerReporter(ServerContext context)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task StatusAsync(string message)
|
||||||
|
{
|
||||||
|
Context.Logger.LogInformation("Status: {message}", message);
|
||||||
|
|
||||||
|
await Context.Server.Console.WriteStdOutAsync(
|
||||||
|
string.Format(StatusTemplate, message)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ErrorAsync(string message)
|
||||||
|
{
|
||||||
|
Context.Logger.LogError("Error: {message}", message);
|
||||||
|
|
||||||
|
await Context.Server.Console.WriteStdOutAsync(
|
||||||
|
string.Format(ErrorTemplate, message)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync()
|
||||||
|
=> ValueTask.CompletedTask;
|
||||||
|
}
|
||||||
64
MoonlightServers.Daemon/ServerSystem/Interfaces/IConsole.cs
Normal file
64
MoonlightServers.Daemon/ServerSystem/Interfaces/IConsole.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
|
||||||
|
public interface IConsole : IServerComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Writes to the standard input of the console. If attached to the runtime when using docker for example this
|
||||||
|
/// would write into the containers standard input.
|
||||||
|
/// <remarks>This method does not add a newline separator at the end of the content. The caller needs to add this themselves if required</remarks>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content">Content to write</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task WriteStdInAsync(string content);
|
||||||
|
/// <summary>
|
||||||
|
/// Writes to the standard output of the console. If attached to the runtime when using docker for example this
|
||||||
|
/// would write into the containers standard output.
|
||||||
|
/// <remarks>This method does not add a newline separator at the end of the content. The caller needs to add this themselves if required</remarks>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content">Content to write</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task WriteStdOutAsync(string content);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attaches the console to the runtime environment
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task AttachRuntimeAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attaches the console to the installation environment
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task AttachInstallationAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches all output from the runtime environment and write them into the cache without triggering any events
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task FetchRuntimeAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches all output from the installation environment and write them into the cache without triggering any events
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task FetchInstallationAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the cache of the standard output received by the environments
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task ClearCacheAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the content from the standard output cache
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Content from the cache</returns>
|
||||||
|
public Task<IEnumerable<string>> GetCacheAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribes to standard output receive events
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">Callback which will be invoked whenever a new line is received</param>
|
||||||
|
/// <returns>Subscription disposable to unsubscribe from the event</returns>
|
||||||
|
public Task<IAsyncDisposable> SubscribeStdOutAsync(Func<string, ValueTask> callback);
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
|
||||||
|
public interface IFileSystem : IServerComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the path of the file system on the host operating system to be reused by other components
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Path to the file systems storage location</returns>
|
||||||
|
public Task<string> GetPathAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the file system exists
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if it does exist. False if it doesn't exist</returns>
|
||||||
|
public Task<bool> CheckExistsAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the file system is mounted
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if its mounted, False if it is not mounted</returns>
|
||||||
|
public Task<bool> CheckMountedAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the file system. E.g. Creating a virtual disk, formatting it
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task CreateAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs checks and optimisations on the file system.
|
||||||
|
/// E.g. checking for corrupted files, resizing a virtual disk or adjusting file permissions
|
||||||
|
/// <remarks>Requires <see cref="MountAsync"/> to be called before or the file system to be in a mounted state</remarks>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task PerformChecksAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mounts the file system
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task MountAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unmounts the file system
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task UnmountAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Destroys the file system and its contents
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task DestroyAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
|
||||||
|
public interface IInstallation : IServerComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the installation environment exists. It doesn't matter if it is currently running or not
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if it exists, False if it doesn't</returns>
|
||||||
|
public Task<bool> CheckExistsAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the installation environment
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="runtimePath">Host path of the runtime storage location</param>
|
||||||
|
/// <param name="hostPath">Host path of the installation file system</param>
|
||||||
|
/// <param name="data">Installation data for the server</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task CreateAsync(string runtimePath, string hostPath, ServerInstallDataResponse data);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the installation
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task StartAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kills the current installation immediately
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task KillAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the installation. E.g. removes the docker container
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task DestroyAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribes to the event when the installation exists
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">Callback to invoke whenever the installation exists</param>
|
||||||
|
/// <returns>Subscription disposable to unsubscribe from the event</returns>
|
||||||
|
public Task<IAsyncDisposable> SubscribeExitedAsync(Func<int, ValueTask> callback);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connects an existing installation to this abstraction in order to restore it.
|
||||||
|
/// E.g. fetching the container id and using it for exit events
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task RestoreAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
|
||||||
|
public interface IOnlineDetector : IServerComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the detection engine for the online state
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task CreateAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the detection of the online state based on the received output
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">Excerpt of the output</param>
|
||||||
|
/// <returns>True if the detection showed that the server is online. False if the detection didnt find anything</returns>
|
||||||
|
public Task<bool> HandleOutputAsync(string line);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Destroys the detection engine for the online state
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task DestroyAsync();
|
||||||
|
}
|
||||||
18
MoonlightServers.Daemon/ServerSystem/Interfaces/IReporter.cs
Normal file
18
MoonlightServers.Daemon/ServerSystem/Interfaces/IReporter.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
|
||||||
|
public interface IReporter : IServerComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Writes both in the server logs as well in the server console the provided message as a status update
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">Message to write</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task StatusAsync(string message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes both in the server logs as well in the server console the provided message as an error
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">Message to write</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task ErrorAsync(string message);
|
||||||
|
}
|
||||||
16
MoonlightServers.Daemon/ServerSystem/Interfaces/IRestorer.cs
Normal file
16
MoonlightServers.Daemon/ServerSystem/Interfaces/IRestorer.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
|
||||||
|
public interface IRestorer : IServerComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Checks for any running runtime environment from which the state can be restored from
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<bool> HandleRuntimeAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks for any running installation environment from which the state can be restored from
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<bool> HandleInstallationAsync();
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user