Merge branch 'main' into unify-pickable-columns
commit
bd2da60792
|
@ -14,7 +14,7 @@ jobs:
|
||||||
id: trim_tag_ref
|
id: trim_tag_ref
|
||||||
with:
|
with:
|
||||||
string: '${{ github.ref }}'
|
string: '${{ github.ref }}'
|
||||||
pattern: 'refs/tags/'
|
pattern: 'refs/tags/v'
|
||||||
replace-with: ''
|
replace-with: ''
|
||||||
# Server
|
# Server
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
@ -25,9 +25,15 @@ jobs:
|
||||||
- name: Restore dependencies
|
- name: Restore dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
- name: Build
|
- name: Build
|
||||||
run: dotnet build "./LANCommander/LANCommander.csproj" --no-restore
|
run: dotnet build "./LANCommander/LANCommander.csproj" --no-restore /p:Version="${{ steps.trim_tag_ref.outputs.replaced }}"
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: dotnet publish "./LANCommander/LANCommander.csproj" -c Release -o _Build --self-contained --os win -p:PublishSingleFile=true
|
run: dotnet publish "./LANCommander/LANCommander.csproj" -c Release -o _Build --self-contained --os win -p:PublishSingleFile=true
|
||||||
|
- name: Sign Windows Binary
|
||||||
|
uses: nadeemjazmawe/Sign-action-signtool.exe@v0.1
|
||||||
|
with:
|
||||||
|
certificate: "${{ secrets.CERTIFICATE }}"
|
||||||
|
cert-password: "${{ secrets.CERTIFICATE_PASSWORD }}"
|
||||||
|
filepath: "./_Build/LANCommander.exe"
|
||||||
- name: Upload Artifacts
|
- name: Upload Artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
|
@ -42,7 +48,13 @@ jobs:
|
||||||
- name: Restore NuGet packages
|
- name: Restore NuGet packages
|
||||||
run: nuget restore LANCommander.sln
|
run: nuget restore LANCommander.sln
|
||||||
- name: Build and Publish Library
|
- name: Build and Publish Library
|
||||||
run: msbuild LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj /p:Configuration=Release /p:OutputPath=Build
|
run: msbuild LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj /p:Configuration=Release /p:OutputPath=Build /p:Version="${{ steps.trim_tag_ref.outputs.replaced }}"
|
||||||
|
- name: Sign Windows Binary
|
||||||
|
uses: nadeemjazmawe/Sign-action-signtool.exe@v0.1
|
||||||
|
with:
|
||||||
|
certificate: "${{ secrets.CERTIFICATE }}"
|
||||||
|
cert-password: "${{ secrets.CERTIFICATE_PASSWORD }}"
|
||||||
|
filepath: "LANCommander.Playnite.Extension/Build/LANCommander.Playnite.Extension.dll"
|
||||||
- name: Download Playnite Release
|
- name: Download Playnite Release
|
||||||
uses: robinraju/release-downloader@v1.7
|
uses: robinraju/release-downloader@v1.7
|
||||||
with:
|
with:
|
||||||
|
@ -51,12 +63,19 @@ jobs:
|
||||||
fileName: Playnite1018.zip
|
fileName: Playnite1018.zip
|
||||||
- name: Extract Playnite
|
- name: Extract Playnite
|
||||||
run: Expand-Archive -Path Playnite1018.zip -DestinationPath Playnite
|
run: Expand-Archive -Path Playnite1018.zip -DestinationPath Playnite
|
||||||
|
- name: Update Manifest Versioning
|
||||||
|
uses: fjogeleit/yaml-update-action@main
|
||||||
|
with:
|
||||||
|
valueFile: "LANCommander.Playnite.Extension/Build/extension.yaml"
|
||||||
|
propertyPath: "Version"
|
||||||
|
value: "${{ steps.trim_tag_ref.outputs.replaced }}"
|
||||||
|
commitChange: false
|
||||||
- name: Run Playnite Toolbox
|
- name: Run Playnite Toolbox
|
||||||
run: Playnite/Toolbox.exe pack LANCommander.Playnite.Extension/Build .
|
run: Playnite/Toolbox.exe pack LANCommander.Playnite.Extension/Build .
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v3.1.2
|
uses: actions/upload-artifact@v3.1.2
|
||||||
with:
|
with:
|
||||||
name: LANCommander.PlaynitePlugin-${{ steps.trim_tag_ref.outputs.replaced }}
|
name: LANCommander.PlaynitePlugin-v${{ steps.trim_tag_ref.outputs.replaced }}
|
||||||
path: LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_1_0.pext
|
path: LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_*.pext
|
||||||
# Release
|
# Release
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,12 @@ jobs:
|
||||||
run: dotnet build "./LANCommander/LANCommander.csproj" --no-restore
|
run: dotnet build "./LANCommander/LANCommander.csproj" --no-restore
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: dotnet publish "./LANCommander/LANCommander.csproj" -c Release -o _Build --self-contained --os win -p:PublishSingleFile=true
|
run: dotnet publish "./LANCommander/LANCommander.csproj" -c Release -o _Build --self-contained --os win -p:PublishSingleFile=true
|
||||||
|
- name: Sign Windows Binary
|
||||||
|
uses: nadeemjazmawe/Sign-action-signtool.exe@v0.1
|
||||||
|
with:
|
||||||
|
certificate: "${{ secrets.CERTIFICATE }}"
|
||||||
|
cert-password: "${{ secrets.CERTIFICATE_PASSWORD }}"
|
||||||
|
filepath: "./_Build/LANCommander.exe"
|
||||||
- name: Upload Artifacts
|
- name: Upload Artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.52" />
|
||||||
<PackageReference Include="System.Text.Json" Version="5.0.1" />
|
<PackageReference Include="System.Text.Json" Version="5.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,34 @@ namespace LANCommander.PlaynitePlugin
|
||||||
PowerShellRuntime = new PowerShellRuntime();
|
PowerShellRuntime = new PowerShellRuntime();
|
||||||
|
|
||||||
GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime);
|
GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime);
|
||||||
|
|
||||||
|
api.UriHandler.RegisterSource("lancommander", args =>
|
||||||
|
{
|
||||||
|
if (args.Arguments.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Guid gameId;
|
||||||
|
|
||||||
|
switch (args.Arguments[0].ToLower())
|
||||||
|
{
|
||||||
|
case "install":
|
||||||
|
if (args.Arguments.Length == 1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (Guid.TryParse(args.Arguments[1], out gameId))
|
||||||
|
PlayniteApi.InstallGame(gameId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "run":
|
||||||
|
if (args.Arguments.Length == 1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (Guid.TryParse(args.Arguments[1], out gameId))
|
||||||
|
PlayniteApi.StartGame(gameId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnApplicationStarted(OnApplicationStartedEventArgs args)
|
public override void OnApplicationStarted(OnApplicationStartedEventArgs args)
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
ViewData["Title"] = "First Time Setup";
|
ViewData["Title"] = "First Time Setup";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="ant-row ant-row-middle ant-row-space-around" style="position: absolute; top:0; left: 0; right: 0; bottom: 0;">
|
<div class="ant-row ant-row-middle ant-row-space-around" style="min-height: 100vh; margin-top: -24px;">
|
||||||
<div class="ant-col ant-col-10">
|
<div class="ant-col ant-col-xs-24 ant-col-md-10">
|
||||||
|
|
||||||
<div style="text-align: center; margin-bottom: 24px;">
|
<div style="text-align: center; margin-bottom: 24px;">
|
||||||
<img src="~/static/logo.svg" />
|
<img src="~/static/logo.svg" />
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
ViewData["Title"] = "Log in";
|
ViewData["Title"] = "Log in";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="ant-row ant-row-middle ant-row-space-around" style="position: absolute; top:0; left: 0; right: 0; bottom: 0;">
|
<div class="ant-row ant-row-middle ant-row-space-around" style="min-height: 100vh; margin-top: -24px;">
|
||||||
<div class="ant-col ant-col-10">
|
<div class="ant-col ant-col-xs-24 ant-col-md-10">
|
||||||
|
|
||||||
<div style="text-align: center; margin-bottom: 24px;">
|
<div style="text-align: center; margin-bottom: 24px;">
|
||||||
<img src="~/static/logo.svg" />
|
<img src="~/static/logo.svg" />
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
ViewData["Title"] = "Register";
|
ViewData["Title"] = "Register";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="ant-row ant-row-middle ant-row-space-around" style="position: absolute; top:0; left: 0; right: 0; bottom: 0;">
|
<div class="ant-row ant-row-middle ant-row-space-around" style="min-height: 100vh; margin-top: -24px;">
|
||||||
<div class="ant-col ant-col-10">
|
<div class="ant-col ant-col-xs-24 ant-col-md-10">
|
||||||
|
|
||||||
<div style="text-align: center; margin-bottom: 24px;">
|
<div style="text-align: center; margin-bottom: 24px;">
|
||||||
<img src="~/static/logo.svg" />
|
<img src="~/static/logo.svg" />
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<Header>
|
||||||
|
<div class="logo" style="background: url('/static/logo-dark.svg'); width: 143px; height: 31px; margin: 16px 24px 16px 0; float: left; background-size: contain;" />
|
||||||
|
|
||||||
|
<Menu Theme="MenuTheme.Dark" Mode="MenuMode.Horizontal">
|
||||||
|
@ChildContent
|
||||||
|
</Menu>
|
||||||
|
</Header>
|
||||||
|
|
||||||
|
<MobileMenu>
|
||||||
|
@ChildContent
|
||||||
|
</MobileMenu>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
<div class="mobile-menu">
|
||||||
|
<Header Class="mobile-header">
|
||||||
|
<div class="logo" style="background: url('/static/logo-dark.svg'); width: 143px; height: 31px; background-size: contain;" />
|
||||||
|
<Button Icon="@IconType.Outline.Menu" Type="@ButtonType.Text" OnClick="ToggleMenu" />
|
||||||
|
</Header>
|
||||||
|
|
||||||
|
<Drawer Closable="true" Visible="@MenuDrawerOpen" Placement="@("top")" Class="menu-drawer">
|
||||||
|
<Menu Theme="MenuTheme.Dark" Mode="MenuMode.Vertical">
|
||||||
|
@ChildContent
|
||||||
|
</Menu>
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||||
|
|
||||||
|
bool MenuDrawerOpen = true;
|
||||||
|
|
||||||
|
protected override void OnAfterRender(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
NavigationManager.LocationChanged += CloseMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ToggleMenu()
|
||||||
|
{
|
||||||
|
MenuDrawerOpen = !MenuDrawerOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CloseMenu(object? sender, LocationChangedEventArgs e)
|
||||||
|
{
|
||||||
|
MenuDrawerOpen = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace LANCommander.Models
|
||||||
|
{
|
||||||
|
public class OrphanedFile
|
||||||
|
{
|
||||||
|
public string Path { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
public DateTime CreatedOn { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
<PageHeader Title="Dashboard" Style="margin-bottom: 24px" />
|
<PageHeader Title="Dashboard" Style="margin-bottom: 24px" />
|
||||||
|
|
||||||
<GridRow Gutter="(16, 16)">
|
<GridRow Gutter="(16, 16)">
|
||||||
<GridCol Sm="24" Md="12">
|
<GridCol Xs="24" Md="12">
|
||||||
<Card Title="Network Download Rate">
|
<Card Title="Network Download Rate">
|
||||||
<Body>
|
<Body>
|
||||||
<NetworkDownloadRate TimerHistory="60" TimerInterval="1000" />
|
<NetworkDownloadRate TimerHistory="60" TimerInterval="1000" />
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
</Card>
|
</Card>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
|
|
||||||
<GridCol Sm="24" Md="12">
|
<GridCol Xs="24" Md="12">
|
||||||
<Card Title="Network Upload Rate">
|
<Card Title="Network Upload Rate">
|
||||||
<Body>
|
<Body>
|
||||||
<NetworkUploadRate TimerHistory="60" TimerInterval="1000" />
|
<NetworkUploadRate TimerHistory="60" TimerInterval="1000" />
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
</Card>
|
</Card>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
|
|
||||||
<GridCol Sm="24" Md="12">
|
<GridCol Xs="24" Md="12">
|
||||||
<Card Title="CPU Usage (%)">
|
<Card Title="CPU Usage (%)">
|
||||||
<Body>
|
<Body>
|
||||||
<ProcessorUtilization TimerHistory="60" TimerInterval="1000" />
|
<ProcessorUtilization TimerHistory="60" TimerInterval="1000" />
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
</Card>
|
</Card>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
|
|
||||||
<GridCol Sm="24" Md="12">
|
<GridCol Xs="24" Md="12">
|
||||||
<Card Title="Storage Usage">
|
<Card Title="Storage Usage">
|
||||||
<Body>
|
<Body>
|
||||||
<StorageUsage />
|
<StorageUsage />
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<Space Direction="DirectionVHType.Horizontal">
|
<Space Direction="DirectionVHType.Horizontal">
|
||||||
<SpaceItem>
|
<SpaceItem>
|
||||||
<InputFile id="FileInput" OnChange="FileSelected" hidden />
|
<InputFile @ref="FileInput" id="FileInput" OnChange="FileSelected" hidden />
|
||||||
<Upload Name="files" FileList="FileList">
|
<Upload Name="files" FileList="FileList">
|
||||||
<label class="ant-btn" for="FileInput">
|
<label class="ant-btn" for="FileInput">
|
||||||
<Icon Type="upload" />
|
<Icon Type="upload" />
|
||||||
|
@ -66,6 +66,7 @@
|
||||||
|
|
||||||
Archive Archive;
|
Archive Archive;
|
||||||
|
|
||||||
|
InputFile FileInput;
|
||||||
IBrowserFile File { get; set; }
|
IBrowserFile File { get; set; }
|
||||||
List<UploadFileItem> FileList = new List<UploadFileItem>();
|
List<UploadFileItem> FileList = new List<UploadFileItem>();
|
||||||
|
|
||||||
|
@ -120,14 +121,24 @@
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
await Task.Delay(500);
|
var i = 0;
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(archive.ObjectKey) && archive.ObjectKey != Guid.Empty.ToString())
|
// Check every 10 seconds to see if the file input is available
|
||||||
await JS.InvokeVoidAsync("Uploader.Init", "FileInput", archive.ObjectKey.ToString());
|
while (i < 20)
|
||||||
else
|
{
|
||||||
await JS.InvokeVoidAsync("Uploader.Init", "FileInput", "");
|
if (FileInput != null)
|
||||||
|
{
|
||||||
|
if (!String.IsNullOrWhiteSpace(archive.ObjectKey) && archive.ObjectKey != Guid.Empty.ToString())
|
||||||
|
await JS.InvokeVoidAsync("Uploader.Init", "FileInput", archive.ObjectKey.ToString());
|
||||||
|
else
|
||||||
|
await JS.InvokeVoidAsync("Uploader.Init", "FileInput", "");
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UploadArchiveJS()
|
private async Task UploadArchiveJS()
|
||||||
|
|
|
@ -239,7 +239,7 @@ else
|
||||||
{
|
{
|
||||||
Game = await GameService.Add(Game);
|
Game = await GameService.Add(Game);
|
||||||
|
|
||||||
await MessageService.Success("Game added!");
|
NavigationManager.LocationChanged += NotifyGameAdded;
|
||||||
|
|
||||||
NavigationManager.NavigateTo($"/Games/{Game.Id}");
|
NavigationManager.NavigateTo($"/Games/{Game.Id}");
|
||||||
}
|
}
|
||||||
|
@ -250,6 +250,13 @@ else
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void NotifyGameAdded(object? sender, LocationChangedEventArgs e)
|
||||||
|
{
|
||||||
|
NavigationManager.LocationChanged -= NotifyGameAdded;
|
||||||
|
|
||||||
|
MessageService.Success("Game added!");
|
||||||
|
}
|
||||||
|
|
||||||
private async Task BrowseForIcon()
|
private async Task BrowseForIcon()
|
||||||
{
|
{
|
||||||
var modalOptions = new ModalOptions()
|
var modalOptions = new ModalOptions()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
@inject ServerService ServerService
|
@inject ServerService ServerService
|
||||||
@inject ServerProcessService ServerProcessService
|
@inject ServerProcessService ServerProcessService
|
||||||
@inject IMessageService MessageService
|
@inject IMessageService MessageService
|
||||||
|
@inject INotificationService NotificationService
|
||||||
@implements IAsyncDisposable
|
@implements IAsyncDisposable
|
||||||
|
|
||||||
<Space Size="@("large")">
|
<Space Size="@("large")">
|
||||||
|
@ -58,6 +59,8 @@
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
Server = await ServerService.Get(ServerId);
|
Server = await ServerService.Get(ServerId);
|
||||||
|
|
||||||
|
ServerProcessService.OnStatusUpdate += OnStatusUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnAfterRender(bool firstRender)
|
protected override void OnAfterRender(bool firstRender)
|
||||||
|
@ -73,26 +76,32 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnStatusUpdate(object sender, ServerStatusUpdateEventArgs args)
|
||||||
|
{
|
||||||
|
if (args?.Server?.Id == ServerId)
|
||||||
|
{
|
||||||
|
Status = args.Status;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
if (Status == ServerProcessStatus.Error)
|
||||||
|
{
|
||||||
|
NotificationService.Error(new NotificationConfig
|
||||||
|
{
|
||||||
|
Message = $"Error starting server {args.Server.Name}",
|
||||||
|
Description = args.Exception.Message,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Start()
|
private async Task Start()
|
||||||
{
|
{
|
||||||
try
|
ServerProcessService.StartServerAsync(Server);
|
||||||
{
|
|
||||||
Status = ServerProcessStatus.Starting;
|
|
||||||
|
|
||||||
await ServerProcessService.StartServerAsync(Server);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Status = ServerProcessStatus.Error;
|
|
||||||
|
|
||||||
await MessageService.Error("There was an unexpected error while trying to start the server.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Stop()
|
private void Stop()
|
||||||
{
|
{
|
||||||
Status = ServerProcessStatus.Stopping;
|
|
||||||
|
|
||||||
ServerProcessService.StopServer(Server);
|
ServerProcessService.StopServer(Server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,7 @@
|
||||||
{
|
{
|
||||||
Server = await ServerService.Add(Server);
|
Server = await ServerService.Add(Server);
|
||||||
|
|
||||||
await MessageService.Success("Server added!");
|
NavigationManager.LocationChanged += NotifyServerAdded;
|
||||||
|
|
||||||
NavigationManager.NavigateTo($"/Servers/{Server.Id}");
|
NavigationManager.NavigateTo($"/Servers/{Server.Id}");
|
||||||
}
|
}
|
||||||
|
@ -207,6 +207,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void NotifyServerAdded(object? sender, LocationChangedEventArgs e)
|
||||||
|
{
|
||||||
|
NavigationManager.LocationChanged -= NotifyServerAdded;
|
||||||
|
|
||||||
|
MessageService.Success("Server added!");
|
||||||
|
}
|
||||||
|
|
||||||
private string GetIcon(Game game)
|
private string GetIcon(Game game)
|
||||||
{
|
{
|
||||||
return $"/api/Games/{game?.Id}/Icon.png";
|
return $"/api/Games/{game?.Id}/Icon.png";
|
||||||
|
|
|
@ -10,6 +10,15 @@
|
||||||
<PageHeader Title="Servers">
|
<PageHeader Title="Servers">
|
||||||
<PageHeaderExtra>
|
<PageHeaderExtra>
|
||||||
<Space Direction="DirectionVHType.Horizontal">
|
<Space Direction="DirectionVHType.Horizontal">
|
||||||
|
@if (SelectedServers != null && SelectedServers.Count() > 0)
|
||||||
|
{
|
||||||
|
<SpaceItem>
|
||||||
|
<Button Type="@ButtonType.Primary" OnClick="() => StartServers()">Start</Button>
|
||||||
|
<Popconfirm OnConfirm="() => StopServers()" Title="Are you sure you want to kill these server processes?">
|
||||||
|
<Button Danger Type="@ButtonType.Primary">Stop</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</SpaceItem>
|
||||||
|
}
|
||||||
<SpaceItem>
|
<SpaceItem>
|
||||||
<Search Placeholder="Search" @bind-Value="Search" BindOnInput DebounceMilliseconds="150" OnChange="() => LoadData()" />
|
<Search Placeholder="Search" @bind-Value="Search" BindOnInput DebounceMilliseconds="150" OnChange="() => LoadData()" />
|
||||||
</SpaceItem>
|
</SpaceItem>
|
||||||
|
@ -20,7 +29,8 @@
|
||||||
</PageHeaderExtra>
|
</PageHeaderExtra>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<Table TItem="Server" DataSource="@Servers" Loading="@Loading" PageSize="25">
|
<Table TItem="Server" DataSource="@Servers" Loading="@Loading" PageSize="25" @bind-SelectedRows="SelectedServers">
|
||||||
|
<Selection Key="@(context.Id.ToString())" />
|
||||||
<PropertyColumn Property="s => s.Name" Sortable />
|
<PropertyColumn Property="s => s.Name" Sortable />
|
||||||
<PropertyColumn Property="s => s.Game">
|
<PropertyColumn Property="s => s.Game">
|
||||||
<Image Src="@GetIcon(context.Game)" Height="32" Width="32" Preview="false"></Image>
|
<Image Src="@GetIcon(context.Game)" Height="32" Width="32" Preview="false"></Image>
|
||||||
|
@ -56,6 +66,8 @@
|
||||||
|
|
||||||
string Search = "";
|
string Search = "";
|
||||||
|
|
||||||
|
IEnumerable<Server> SelectedServers;
|
||||||
|
|
||||||
protected override void OnAfterRender(bool firstRender)
|
protected override void OnAfterRender(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
|
@ -102,4 +114,30 @@
|
||||||
{
|
{
|
||||||
return $"/api/Games/{game?.Id}/Icon.png";
|
return $"/api/Games/{game?.Id}/Icon.png";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task StartServers()
|
||||||
|
{
|
||||||
|
foreach (var server in SelectedServers)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var status = ServerProcessService.GetStatus(server);
|
||||||
|
|
||||||
|
if (status == ServerProcessStatus.Stopped || status == ServerProcessStatus.Error)
|
||||||
|
{
|
||||||
|
ServerProcessService.StartServerAsync(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopServers()
|
||||||
|
{
|
||||||
|
foreach (var server in SelectedServers)
|
||||||
|
{
|
||||||
|
if (ServerProcessService.GetStatus(server) == ServerProcessStatus.Running)
|
||||||
|
ServerProcessService.StopServer(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
@page "/Settings/Tools/OrphanedFiles"
|
||||||
|
@using LANCommander.Helpers;
|
||||||
|
@using LANCommander.Models;
|
||||||
|
@layout SettingsLayout
|
||||||
|
@inject ArchiveService ArchiveService;
|
||||||
|
@attribute [Authorize(Roles = "Administrator")]
|
||||||
|
|
||||||
|
<PageHeader Title="Orphaned Files" />
|
||||||
|
|
||||||
|
<div style="padding: 0 24px;">
|
||||||
|
<p>
|
||||||
|
These files exist on the server, but aren't linked in the database. Use this tool to identify and delete orphaned files.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Table TItem="OrphanedFile" DataSource="@Orphans" Loading="@Loading" PageSize="25">
|
||||||
|
<PropertyColumn Property="f => f.Path" />
|
||||||
|
<PropertyColumn Property="f => f.Size" Sortable>
|
||||||
|
@ByteSizeLib.ByteSize.FromBytes(context.Size).ToString()
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="f => f.CreatedOn" Format="MM/dd/yyyy hh:mm tt" Sortable />
|
||||||
|
<ActionColumn Title="" Style="text-align: right">
|
||||||
|
<Space Direction="DirectionVHType.Horizontal">
|
||||||
|
<SpaceItem>
|
||||||
|
<Popconfirm OnConfirm="() => Delete(context)" Title="Are you sure you want to delete this file?">
|
||||||
|
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
|
||||||
|
</Popconfirm>
|
||||||
|
</SpaceItem>
|
||||||
|
</Space>
|
||||||
|
</ActionColumn>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
ICollection<OrphanedFile> Orphans = new List<OrphanedFile>();
|
||||||
|
bool Loading = true;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadData()
|
||||||
|
{
|
||||||
|
Loading = true;
|
||||||
|
|
||||||
|
Orphans = new List<OrphanedFile>();
|
||||||
|
|
||||||
|
var archives = await ArchiveService.Get();
|
||||||
|
var archiveFiles = archives.Select(a => ArchiveService.GetArchiveFileLocation(a));
|
||||||
|
var files = Directory.GetFiles("Upload");
|
||||||
|
|
||||||
|
foreach (var file in files.Where(f => !archiveFiles.Contains(f)))
|
||||||
|
{
|
||||||
|
var fileInfo = new FileInfo(file);
|
||||||
|
|
||||||
|
Orphans.Add(new OrphanedFile
|
||||||
|
{
|
||||||
|
Path = file,
|
||||||
|
Size = fileInfo.Length,
|
||||||
|
CreatedOn = fileInfo.CreationTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Orphans = Orphans.OrderByDescending(f => f.Size).ToList();
|
||||||
|
|
||||||
|
Loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Delete(OrphanedFile file)
|
||||||
|
{
|
||||||
|
FileHelpers.DeleteIfExists(file.Path);
|
||||||
|
|
||||||
|
Orphans.Remove(file);
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,12 @@
|
||||||
<h3>Missing Archives</h3>
|
<h3>Missing Archives</h3>
|
||||||
<p>List and fix all archives that are missing their backing files.</p>
|
<p>List and fix all archives that are missing their backing files.</p>
|
||||||
<a href="/Settings/Tools/MissingArchives" class="ant-btn ant-btn-primary">View Missing Archives</a>
|
<a href="/Settings/Tools/MissingArchives" class="ant-btn ant-btn-primary">View Missing Archives</a>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<h3>Orphaned Files</h3>
|
||||||
|
<p>Find and delete any files that don't exist in the database and may be taking up unnecessary disk space.</p>
|
||||||
|
<a href="/Settings/Tools/OrphanedFiles" class="ant-btn ant-btn-primary">View Orphaned Files</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,16 +11,20 @@ namespace LANCommander.Services
|
||||||
{
|
{
|
||||||
public class GameService : BaseDatabaseService<Game>
|
public class GameService : BaseDatabaseService<Game>
|
||||||
{
|
{
|
||||||
public GameService(DatabaseContext dbContext, IHttpContextAccessor httpContextAccessor) : base(dbContext, httpContextAccessor)
|
private readonly ArchiveService ArchiveService;
|
||||||
|
|
||||||
|
public GameService(DatabaseContext dbContext, IHttpContextAccessor httpContextAccessor, ArchiveService archiveService) : base(dbContext, httpContextAccessor)
|
||||||
{
|
{
|
||||||
|
ArchiveService = archiveService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task Delete(Game game)
|
public override async Task Delete(Game game)
|
||||||
{
|
{
|
||||||
foreach (var archive in game.Archives.OrderByDescending(a => a.CreatedOn))
|
foreach (var archive in game.Archives.OrderByDescending(a => a.CreatedOn))
|
||||||
{
|
{
|
||||||
|
await ArchiveService.Delete(archive);
|
||||||
|
|
||||||
FileHelpers.DeleteIfExists($"Icon/{game.Id}.png".ToPath());
|
FileHelpers.DeleteIfExists($"Icon/{game.Id}.png".ToPath());
|
||||||
FileHelpers.DeleteIfExists($"Upload/{archive.ObjectKey}".ToPath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await base.Delete(game);
|
await base.Delete(game);
|
||||||
|
|
|
@ -33,6 +33,24 @@ namespace LANCommander.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ServerStatusUpdateEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public Server Server { get; private set; }
|
||||||
|
public ServerProcessStatus Status { get; private set; }
|
||||||
|
public Exception Exception { get; private set; }
|
||||||
|
|
||||||
|
public ServerStatusUpdateEventArgs(Server server, ServerProcessStatus status)
|
||||||
|
{
|
||||||
|
Server = server;
|
||||||
|
Status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerStatusUpdateEventArgs(Server server, ServerProcessStatus status, Exception exception) : this(server, status)
|
||||||
|
{
|
||||||
|
Exception = exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class LogFileMonitor : IDisposable
|
public class LogFileMonitor : IDisposable
|
||||||
{
|
{
|
||||||
private ManualResetEvent Latch;
|
private ManualResetEvent Latch;
|
||||||
|
@ -122,6 +140,9 @@ namespace LANCommander.Services
|
||||||
public delegate void OnLogHandler(object sender, ServerLogEventArgs e);
|
public delegate void OnLogHandler(object sender, ServerLogEventArgs e);
|
||||||
public event OnLogHandler OnLog;
|
public event OnLogHandler OnLog;
|
||||||
|
|
||||||
|
public delegate void OnStatusUpdateHandler(object sender, ServerStatusUpdateEventArgs e);
|
||||||
|
public event OnStatusUpdateHandler OnStatusUpdate;
|
||||||
|
|
||||||
private IHubContext<GameServerHub> HubContext;
|
private IHubContext<GameServerHub> HubContext;
|
||||||
|
|
||||||
public ServerProcessService(IHubContext<GameServerHub> hubContext)
|
public ServerProcessService(IHubContext<GameServerHub> hubContext)
|
||||||
|
@ -157,27 +178,42 @@ namespace LANCommander.Services
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
process.Start();
|
try
|
||||||
|
|
||||||
if (!process.StartInfo.UseShellExecute)
|
|
||||||
{
|
{
|
||||||
process.BeginErrorReadLine();
|
OnStatusUpdate?.Invoke(this, new ServerStatusUpdateEventArgs(server, ServerProcessStatus.Starting));
|
||||||
process.BeginOutputReadLine();
|
|
||||||
|
process.Start();
|
||||||
|
|
||||||
|
if (!process.StartInfo.UseShellExecute)
|
||||||
|
{
|
||||||
|
process.BeginErrorReadLine();
|
||||||
|
process.BeginOutputReadLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
Processes[server.Id] = process;
|
||||||
|
|
||||||
|
foreach (var logFile in server.ServerConsoles.Where(sc => sc.Type == ServerConsoleType.LogFile))
|
||||||
|
{
|
||||||
|
StartMonitoringLog(logFile, server);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnStatusUpdate?.Invoke(this, new ServerStatusUpdateEventArgs(server, ServerProcessStatus.Running));
|
||||||
|
|
||||||
|
await process.WaitForExitAsync();
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
Processes[server.Id] = process;
|
|
||||||
|
|
||||||
foreach (var logFile in server.ServerConsoles.Where(sc => sc.Type == ServerConsoleType.LogFile))
|
|
||||||
{
|
{
|
||||||
StartMonitoringLog(logFile, server);
|
OnStatusUpdate?.Invoke(this, new ServerStatusUpdateEventArgs(server, ServerProcessStatus.Error, ex));
|
||||||
}
|
|
||||||
|
|
||||||
await process.WaitForExitAsync();
|
Logger.Error(ex, "Could not start server process");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void StopServer(Server server)
|
public void StopServer(Server server)
|
||||||
{
|
{
|
||||||
|
OnStatusUpdate?.Invoke(this, new ServerStatusUpdateEventArgs(server, ServerProcessStatus.Stopping));
|
||||||
|
|
||||||
if (Processes.ContainsKey(server.Id))
|
if (Processes.ContainsKey(server.Id))
|
||||||
{
|
{
|
||||||
var process = Processes[server.Id];
|
var process = Processes[server.Id];
|
||||||
|
@ -190,6 +226,8 @@ namespace LANCommander.Services
|
||||||
LogFileMonitors[server.Id].Dispose();
|
LogFileMonitors[server.Id].Dispose();
|
||||||
LogFileMonitors.Remove(server.Id);
|
LogFileMonitors.Remove(server.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnStatusUpdate?.Invoke(this, new ServerStatusUpdateEventArgs(server, ServerProcessStatus.Stopped));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartMonitoringLog(ServerConsole log, Server server)
|
private void StartMonitoringLog(ServerConsole log, Server server)
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
<Layout Class="layout">
|
<Layout Class="layout">
|
||||||
<Header>
|
<MainMenu>
|
||||||
<div class="logo" style="background: url('/static/logo-dark.svg'); width: 143px; height: 31px; margin: 16px 24px 16px 0; float: left; background-size: contain;" />
|
<MenuItem RouterLink="/Dashboard">Dashboard</MenuItem>
|
||||||
|
<MenuItem RouterLink="/Games">Games</MenuItem>
|
||||||
|
<MenuItem RouterLink="/Servers">Servers</MenuItem>
|
||||||
|
<MenuItem RouterLink="/Settings">Settings</MenuItem>
|
||||||
|
</MainMenu>
|
||||||
|
|
||||||
<Menu Theme="MenuTheme.Dark" Mode="MenuMode.Horizontal">
|
<Content Style="padding: 24px; min-height: 100vh;">
|
||||||
<MenuItem RouterLink="/Dashboard">Dashboard</MenuItem>
|
|
||||||
<MenuItem RouterLink="/Games">Games</MenuItem>
|
|
||||||
<MenuItem RouterLink="/Servers">Servers</MenuItem>
|
|
||||||
<MenuItem RouterLink="/Settings">Settings</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
</Header>
|
|
||||||
|
|
||||||
<Content Style="padding: 24px;">
|
|
||||||
@Body
|
@Body
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
|
@ -9,7 +9,11 @@
|
||||||
<link href="~/css/site.css" rel="stylesheet" />
|
<link href="~/css/site.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@RenderBody()
|
<section class="layout ant-layout">
|
||||||
|
<main class="ant-layout-content" style="padding: 24px; min-height: 100vh;">
|
||||||
|
@RenderBody()
|
||||||
|
</main>
|
||||||
|
</section>
|
||||||
|
|
||||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||||
<script src="~/lib/antv/g2plot/dist/g2plot.js"></script>
|
<script src="~/lib/antv/g2plot/dist/g2plot.js"></script>
|
||||||
|
|
|
@ -17,4 +17,67 @@
|
||||||
|
|
||||||
.uploader-progress .ant-progress-bg {
|
.uploader-progress .ant-progress-bg {
|
||||||
transition: none;
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-drawer {
|
||||||
|
margin-top: 63px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-drawer .ant-drawer-content {
|
||||||
|
background: #001529;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-drawer .ant-drawer-header-no-title {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-drawer .ant-drawer-body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-drawer .ant-drawer-wrapper,
|
||||||
|
.menu-drawer .ant-drawer-content {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-drawer .ant-drawer-content-wrapper {
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-header .ant-btn {
|
||||||
|
color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.mobile-menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
.mobile-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-header:not(.mobile-header) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-header .logo {
|
||||||
|
margin-right: 0 !important;
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout-content {
|
||||||
|
margin-top: 63px;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue