Started adding basic support for servers

This commit is contained in:
Pat Hartl 2023-03-18 00:44:31 -05:00
parent ac8773c363
commit 4d058649db
11 changed files with 1601 additions and 0 deletions

View file

@ -106,5 +106,7 @@ namespace LANCommander.Data
public DbSet<Key>? Keys { get; set; }
public DbSet<GameSave>? GameSaves { get; set; }
public DbSet<Server>? Servers { get; set; }
}
}

View file

@ -0,0 +1,15 @@
namespace LANCommander.Data.Models
{
public class Server : BaseModel
{
public string Name { get; set; }
public string Path { get; set; }
public string Arguments { get; set; }
public string WorkingDirectory { get; set; }
public string OnStartScriptPath { get; set; }
public string OnStopScriptPath { get; set; }
public bool Autostart { get; set; }
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,61 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
public partial class AddBasicServerModel : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Servers",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
Path = table.Column<string>(type: "TEXT", nullable: false),
Arguments = table.Column<string>(type: "TEXT", nullable: false),
WorkingDirectory = table.Column<string>(type: "TEXT", nullable: false),
OnStartScriptPath = table.Column<string>(type: "TEXT", nullable: false),
OnStopScriptPath = table.Column<string>(type: "TEXT", nullable: false),
Autostart = table.Column<bool>(type: "INTEGER", nullable: false),
CreatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
CreatedById = table.Column<Guid>(type: "TEXT", nullable: true),
UpdatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
UpdatedById = table.Column<Guid>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Servers", x => x.Id);
table.ForeignKey(
name: "FK_Servers_AspNetUsers_CreatedById",
column: x => x.CreatedById,
principalTable: "AspNetUsers",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Servers_AspNetUsers_UpdatedById",
column: x => x.UpdatedById,
principalTable: "AspNetUsers",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_Servers_CreatedById",
table: "Servers",
column: "CreatedById");
migrationBuilder.CreateIndex(
name: "IX_Servers_UpdatedById",
table: "Servers",
column: "UpdatedById");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Servers");
}
}
}

View file

@ -575,6 +575,60 @@ namespace LANCommander.Migrations
b.ToTable("Scripts");
});
modelBuilder.Entity("LANCommander.Data.Models.Server", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Arguments")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("Autostart")
.HasColumnType("INTEGER");
b.Property<Guid?>("CreatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("OnStartScriptPath")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("OnStopScriptPath")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Path")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid?>("UpdatedById")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.Property<string>("WorkingDirectory")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedById");
b.HasIndex("UpdatedById");
b.ToTable("Servers");
});
modelBuilder.Entity("LANCommander.Data.Models.Tag", b =>
{
b.Property<Guid>("Id")
@ -1079,6 +1133,21 @@ namespace LANCommander.Migrations
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("LANCommander.Data.Models.Server", b =>
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById");
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
b.Navigation("CreatedBy");
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("LANCommander.Data.Models.Tag", b =>
{
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")

View file

@ -0,0 +1,66 @@
@page "/Servers/{id:guid}/Edit"
@page "/Servers/Add"
@inject ServerService ServerService
@inject IMessageService MessageService
@inject NavigationManager NavigationManager
<Card Title="Server Details">
<Body>
<Form Model="@Server" Layout="@FormLayout.Vertical">
<FormItem Label="Name">
<Input @bind-Value="@context.Name" />
</FormItem>
<FormItem Label="Path">
<Input @bind-Value="@context.Path" />
</FormItem>
<FormItem Label="Arguments">
<Input @bind-Value="@context.Arguments" />
</FormItem>
<FormItem Label="Working Directory">
<Input @bind-Value="@context.WorkingDirectory" />
</FormItem>
<FormItem>
<Button Type="@ButtonType.Primary" OnClick="Save" Icon="@IconType.Fill.Save">Save</Button>
</FormItem>
</Form>
</Body>
</Card>
@code {
[Parameter] public Guid Id { get; set; }
Server Server;
protected override async Task OnInitializedAsync()
{
if (Id == Guid.Empty)
Server = new Server();
else
Server = await ServerService.Get(Id);
}
private async Task Save()
{
try
{
if (Server.Id != Guid.Empty)
{
Server = await ServerService.Update(Server);
await MessageService.Success("Server updated!");
}
else
{
Server = await ServerService.Add(Server);
await MessageService.Success("Server added!");
NavigationManager.NavigateTo($"/Servers/{Server.Id}/Edit");
}
}
catch (Exception ex)
{
await MessageService.Error("Could not save!");
}
}
}

View file

@ -0,0 +1,88 @@
@page "/Servers"
@attribute [Authorize]
@inject ServerService ServerService
@inject ServerProcessService ServerProcessService
@inject NavigationManager NavigationManager
<PageHeader Title="Servers">
<PageHeaderExtra>
<Button OnClick="() => Add()" Type="@ButtonType.Primary">Add Server</Button>
</PageHeaderExtra>
</PageHeader>
<Table TItem="Server" DataSource="@Servers" Loading="@Loading">
<PropertyColumn Property="s => s.Name" Sortable />
<PropertyColumn Property="s => s.CreatedOn" Format="MM/dd/yyyy hh:mm tt" Sortable />
<PropertyColumn Property="s => s.CreatedBy" Sortable>
@context.CreatedBy?.UserName
</PropertyColumn>
<PropertyColumn Property="g => g.UpdatedOn" Format="MM/dd/yyyy hh:mm tt" Sortable />
<PropertyColumn Property="g => g.UpdatedBy" Sortable>
@context.UpdatedBy?.UserName
</PropertyColumn>
<ActionColumn Title="">
<Space Direction="DirectionVHType.Horizontal">
<SpaceItem>
<Button OnClick="() => Edit(context)">Edit</Button>
<Button OnClick="() => Start(context)">Start</Button>
<Button OnClick="() => Stop(context)">Stop</Button>
</SpaceItem>
<SpaceItem>
<Popconfirm OnConfirm="() => Delete(context)" Title="Are you sure you want to delete this game?">
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
</Popconfirm>
</SpaceItem>
</Space>
</ActionColumn>
</Table>
@code {
IEnumerable<Server> Servers { get; set; } = new List<Server>();
bool Loading = true;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Servers = ServerService.Get().OrderBy(s => s.Name).ToList();
Loading = false;
StateHasChanged();
}
}
private void Add()
{
NavigationManager.NavigateTo("/Servers/Add");
}
private void Edit(Server server)
{
NavigationManager.NavigateTo($"/Servers/{server.Id}/Edit");
}
private void Start(Server server)
{
ServerProcessService.StartServer(server);
}
private void Stop(Server server)
{
ServerProcessService.StopServer(server);
}
private async Task Delete(Server server)
{
Servers = new List<Server>();
Loading = true;
await ServerService.Delete(server);
Servers = ServerService.Get().OrderBy(s => s.Name).ToList();
Loading = false;
}
}

View file

@ -78,6 +78,7 @@ builder.Services.AddControllers().AddJsonOptions(x =>
x.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles;
});
builder.Services.AddFusionCache();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
@ -108,6 +109,9 @@ builder.Services.AddScoped<KeyService>();
builder.Services.AddScoped<TagService>();
builder.Services.AddScoped<CompanyService>();
builder.Services.AddScoped<IGDBService>();
builder.Services.AddScoped<ServerService>();
builder.Services.AddSingleton<ServerProcessService>();
if (settings.Beacon)
builder.Services.AddHostedService<BeaconService>();

View file

@ -0,0 +1,33 @@
using LANCommander.Data.Models;
using System.Diagnostics;
namespace LANCommander.Services
{
public class ServerProcessService
{
public Dictionary<Guid, Process> Processes = new Dictionary<Guid, Process>();
public Dictionary<Guid, int> Threads { get; set; } = new Dictionary<Guid, int>();
public async Task StartServer(Server server)
{
var process = new Process();
process.StartInfo.FileName = server.Path;
process.StartInfo.WorkingDirectory = server.WorkingDirectory;
process.StartInfo.Arguments = server.Arguments;
process.StartInfo.UseShellExecute = true;
process.Start();
Processes[server.Id] = process;
await process.WaitForExitAsync();
}
public void StopServer(Server server)
{
if (Processes.ContainsKey(server.Id))
Processes[server.Id].Kill();
}
}
}

View file

@ -0,0 +1,16 @@
using LANCommander.Data;
using LANCommander.Data.Models;
using System.Diagnostics;
using ZiggyCreatures.Caching.Fusion;
namespace LANCommander.Services
{
public class ServerService : BaseDatabaseService<Server>
{
private IFusionCache Cache;
public ServerService(DatabaseContext dbContext, IHttpContextAccessor httpContextAccessor, IFusionCache cache) : base(dbContext, httpContextAccessor)
{
Cache = cache;
}
}
}

View file

@ -7,6 +7,7 @@
<Menu Theme="MenuTheme.Dark" Mode="MenuMode.Horizontal">
<MenuItem RouterLink="/Dashboard">Dashboard</MenuItem>
<MenuItem RouterLink="/Games">Games</MenuItem>
<MenuItem RouterLink="/Servers">Servers</MenuItem>
<MenuItem RouterLink="/Settings">Settings</MenuItem>
</Menu>
</Header>