Refactored ui. Improved console experience. Added command endpoint

This commit is contained in:
2025-07-18 21:16:52 +02:00
parent f8c11b2dd8
commit 265a4b280b
43 changed files with 479 additions and 149 deletions

View File

@@ -0,0 +1,110 @@
@using MoonCore.Blazor.FlyonUi.Alerts
@using MoonCore.Blazor.FlyonUi.Components
@using MoonCore.Blazor.FlyonUi.Modals
@using MoonCore.Blazor.FlyonUi.Toasts
@using MoonCore.Helpers
@using MoonCore.Models
@using MoonlightServers.Frontend.UI.Components.Stars.Modals
@using MoonlightServers.Shared.Http.Requests.Admin.StarDockerImages
@using MoonlightServers.Shared.Http.Responses.Admin.StarDockerImages
@using MoonlightServers.Shared.Http.Responses.Admin.Stars
@inject HttpApiClient ApiClient
@inject ModalService ModalService
@inject ToastService ToastService
@inject AlertService AlertService
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="flex justify-end">
<button type="button" @onclick="AddDockerImage" class="btn btn-primary">Add docker image</button>
</div>
<div class="grid sm:grid-cols-2 xl:grid-cols-3 gap-4 mt-5">
@foreach (var dockerImage in DockerImages)
{
<div class="col-span-1 card card-body p-2.5">
<div class="flex items-center justify-between">
<div class="ml-3">
<i class="icon-container text-xl align-middle mr-2"></i>
<span class="align-middle text-lg">@dockerImage.DisplayName</span>
</div>
<div class="gap-x-2">
<button type="button" @onclick="() => UpdateDockerImage(dockerImage)" class="btn btn-primary">
<i class="icon-settings text-base"></i>
</button>
<button type="button" @onclick="() => DeleteDockerImage(dockerImage)" class="btn btn-error">
<i class="icon-trash text-base"></i>
</button>
</div>
</div>
</div>
}
</div>
</LazyLoader>
@code
{
[Parameter] public StarDetailResponse Star { get; set; }
private StarDockerImageDetailResponse[] DockerImages;
private LazyLoader LazyLoader;
private async Task Load(LazyLoader _)
{
var pagedVariables = await ApiClient.GetJson<PagedData<StarDockerImageDetailResponse>>(
$"api/admin/servers/stars/{Star.Id}/dockerImages?page=0&pageSize=50"
);
DockerImages = pagedVariables.Items;
}
private async Task AddDockerImage()
{
Func<CreateStarDockerImageRequest, Task> onSubmit = async request =>
{
await ApiClient.Post($"api/admin/servers/stars/{Star.Id}/dockerImages", request);
await ToastService.Success("Successfully created docker image");
await LazyLoader.Reload();
};
await ModalService.Launch<CreateDockerImageModal>(parameters =>
{
parameters.Add("OnSubmit", onSubmit);
});
}
private async Task UpdateDockerImage(StarDockerImageDetailResponse dockerImage)
{
Func<UpdateStarDockerImageRequest, Task> onSubmit = async request =>
{
await ApiClient.Patch($"api/admin/servers/stars/{Star.Id}/dockerImages/{dockerImage.Id}", request);
await ToastService.Success("Successfully updated docker image");
await LazyLoader.Reload();
};
await ModalService.Launch<UpdateDockerImageModal>(parameters =>
{
parameters.Add("OnSubmit", onSubmit);
parameters.Add("DockerImage", dockerImage);
});
}
private async Task DeleteDockerImage(StarDockerImageDetailResponse dockerImage)
{
await AlertService.ConfirmDanger(
"Delete docker image",
"Do you really want to delete the selected docker image? This cannot be undone",
async () =>
{
await ApiClient.Delete($"api/admin/servers/stars/{Star.Id}/dockerImages/{dockerImage.Id}");
await ToastService.Success("Successfully deleted docker image");
await LazyLoader.Reload();
}
);
}
}

View File

@@ -0,0 +1,41 @@
@using MoonlightServers.Shared.Http.Requests.Admin.Stars
<div>
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div class="sm:col-span-2">
<label class="block text-sm font-medium leading-6 text-base-content">Name</label>
<div class="mt-2">
<input @bind="Request.Name" type="text" autocomplete="off" class="input w-full">
</div>
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium leading-6 text-base-content">Version</label>
<div class="mt-2">
<input @bind="Request.Version" type="text" autocomplete="off" class="input w-full">
</div>
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium leading-6 text-base-content">Author</label>
<div class="mt-2">
<input @bind="Request.Author" type="text" autocomplete="off" class="input w-full">
</div>
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium leading-6 text-base-content">Donate Url</label>
<div class="mt-2">
<input @bind="Request.DonateUrl" type="text" autocomplete="off" class="input w-full">
</div>
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium leading-6 text-base-content">Update Url</label>
<div class="mt-2">
<input @bind="Request.UpdateUrl" type="text" autocomplete="off" class="input w-full">
</div>
</div>
</div>
</div>
@code
{
[Parameter] public UpdateStarRequest Request { get; set; }
}

View File

@@ -0,0 +1,42 @@
@using MoonCore.Blazor.FlyonUi.Ace
@using MoonlightServers.Shared.Http.Requests.Admin.Stars
<div>
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div class="sm:col-span-3">
<label class="block text-sm font-medium leading-6 text-base-content">Docker Image</label>
<div class="mt-2">
<input @bind="Request.InstallDockerImage" type="text" autocomplete="off" class="input w-full">
</div>
</div>
<div class="sm:col-span-3">
<label class="block text-sm font-medium leading-6 text-base-content">Shell</label>
<div class="mt-2">
<input @bind="Request.InstallShell" type="text" autocomplete="off" class="input w-full">
</div>
</div>
<div class="sm:col-span-6">
<label class="block text-sm font-medium leading-6 text-base-content">Script</label>
<div class="mt-2" @onfocusout="OnFocusOut">
<CodeEditor @ref="CodeEditor" InitialContent="@Request.InstallScript" OnConfigure="OnConfigure" />
</div>
</div>
</div>
</div>
@code
{
[Parameter] public UpdateStarRequest Request { get; set; }
private CodeEditor CodeEditor;
private void OnConfigure(CodeEditorOptions options)
{
options.Mode = "ace/mode/sh";
}
private async Task OnFocusOut()
{
Request.InstallScript = await CodeEditor.GetValue();
}
}

View File

@@ -0,0 +1,67 @@
@using MoonCore.Blazor.FlyonUi.Components
@using MoonCore.Helpers
@using MoonCore.Models
@using MoonlightServers.Shared.Http.Requests.Admin.Stars
@using MoonlightServers.Shared.Http.Responses.Admin.StarDockerImages
@using MoonlightServers.Shared.Http.Responses.Admin.Stars
@using MoonlightServers.Frontend.UI.Components.Forms
@inject HttpApiClient ApiClient
<LazyLoader Load="Load">
<div>
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div class="sm:col-span-2">
<label class="block text-sm font-medium leading-6 text-base-content">Allow docker image change</label>
<div class="mt-2">
<Switch @bind-Value="Request.AllowDockerImageChange" />
</div>
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium leading-6 text-base-content">Default docker image</label>
<div class="mt-2">
<select @bind="Request.DefaultDockerImage" class="select w-full">
@foreach (var dockerImage in DockerImages)
{
var index = DockerImages.IndexOf(dockerImage);
if (Request.DefaultDockerImage == index)
{
<option selected="selected"
value="@index">
@dockerImage.DisplayName
</option>
}
else
{
<option value="@index">
@dockerImage.DisplayName
</option>
}
}
</select>
</div>
</div>
</div>
</div>
</LazyLoader>
@code
{
[Parameter] public UpdateStarRequest Request { get; set; }
[Parameter] public StarDetailResponse Star { get; set; }
private List<StarDockerImageDetailResponse> DockerImages;
private async Task Load(LazyLoader _)
{
var pagedVariables = await ApiClient.GetJson<PagedData<StarDockerImageDetailResponse>>(
$"api/admin/servers/stars/{Star.Id}/dockerImages?page=0&pageSize=50"
);
DockerImages = pagedVariables
.Items
.OrderBy(x => x.Id) // Make sure its in the correct order every time
.ToList();
}
}

View File

@@ -0,0 +1,134 @@
@using System.Text.Json
@using Microsoft.Extensions.Logging
@using MoonCore.Blazor.FlyonUi.Alerts
@using MoonCore.Blazor.FlyonUi.Modals
@using MoonCore.Blazor.FlyonUi.Toasts
@using MoonlightServers.Frontend.UI.Components.Stars.Modals
@using MoonlightServers.Shared.Http.Requests.Admin.Stars
@using MoonlightServers.Shared.Models
@inject ILogger<ParseConfig> Logger
@inject ModalService ModalService
@inject AlertService AlertService
@inject ToastService ToastService
<div class="flex justify-end mb-5">
<button type="button" @onclick="AddConfig" class="btn btn-primary">Add parse configuration</button>
</div>
@if (HasParseError)
{
}
else
{
<div class="grid sm:grid-cols-2 xl:grid-cols-3 gap-4">
@foreach (var configuration in Configurations)
{
<div class="col-span-1 card card-body p-2.5">
<div class="flex items-center justify-between">
<div class="ml-3">
<i class="icon-file-cog text-xl align-middle mr-2"></i>
<span class="align-middle text-lg">@configuration.File</span>
</div>
<div class="gap-x-2">
<button type="button" @onclick="() => UpdateConfig(configuration)" class="btn btn-primary">
<i class="icon-settings text-base"></i>
</button>
<button type="button" @onclick="() => DeleteConfig(configuration)" class="btn btn-error">
<i class="icon-trash text-base"></i>
</button>
</div>
</div>
</div>
}
</div>
}
@code
{
[Parameter] public UpdateStarRequest Request { get; set; }
private List<ParseConfiguration> Configurations;
private bool HasParseError = false;
protected override Task OnInitializedAsync()
{
ReadFromJson();
return Task.CompletedTask;
}
private async Task AddConfig()
{
Func<ParseConfiguration, Task> onSubmit = async configuration =>
{
Configurations.Add(configuration);
SaveChanges();
await InvokeAsync(StateHasChanged);
await ToastService.Success("Successfully created parse configuration");
};
await ModalService.Launch<CreateParseConfigModal>(parameters =>
{
parameters.Add("OnSubmit", onSubmit);
}, "max-w-xl");
}
private async Task UpdateConfig(ParseConfiguration configuration)
{
Func<ParseConfiguration, Task> onSubmit = async _ =>
{
SaveChanges();
await InvokeAsync(StateHasChanged);
await ToastService.Success("Successfully updated parse configuration");
};
await ModalService.Launch<UpdateParseConfigModal>(parameters =>
{
parameters.Add("OnSubmit", onSubmit);
parameters.Add("Configuration", configuration);
}, "max-w-xl");
}
private async Task DeleteConfig(ParseConfiguration configuration)
{
await AlertService.ConfirmDanger(
"Parse configuration deletion",
"Do you really want to delete the selected parse configuration",
async () =>
{
Configurations.Remove(configuration);
SaveChanges();
await InvokeAsync(StateHasChanged);
await ToastService.Success("Successfully deleted parse configuration");
}
);
}
private void SaveChanges()
{
Request.ParseConfiguration = JsonSerializer.Serialize(Configurations);
ReadFromJson();
}
private void ReadFromJson()
{
try
{
Configurations = JsonSerializer.Deserialize<List<ParseConfiguration>>(Request.ParseConfiguration)
?? throw new ArgumentNullException();
HasParseError = false;
}
catch (Exception e)
{
Logger.LogWarning("An error occured while reading parse configuration: {e}", e);
HasParseError = true;
}
}
}

View File

@@ -0,0 +1,28 @@
@using MoonlightServers.Shared.Http.Requests.Admin.Stars
<div>
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div class="sm:col-span-4">
<label class="block text-sm font-medium leading-6 text-base-content">Startup Command</label>
<div class="mt-2">
<input @bind="Request.StartupCommand" type="text" autocomplete="off" class="input w-full">
</div>
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium leading-6 text-base-content">Stop Command</label>
<div class="mt-2">
<input @bind="Request.StopCommand" type="text" autocomplete="off" class="input w-full">
</div>
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium leading-6 text-base-content">Online Detection</label>
<div class="mt-2">
<input @bind="Request.OnlineDetection" type="text" autocomplete="off" class="input w-full">
</div>
</div>
</div>
</div>
@code
{
[Parameter] public UpdateStarRequest Request { get; set; }
}

View File

@@ -0,0 +1,110 @@
@using MoonCore.Blazor.FlyonUi.Alerts
@using MoonCore.Blazor.FlyonUi.Components
@using MoonCore.Blazor.FlyonUi.Modals
@using MoonCore.Blazor.FlyonUi.Toasts
@using MoonCore.Helpers
@using MoonCore.Models
@using MoonlightServers.Frontend.UI.Components.Stars.Modals
@using MoonlightServers.Shared.Http.Requests.Admin.StarVariables
@using MoonlightServers.Shared.Http.Responses.Admin.Stars
@using MoonlightServers.Shared.Http.Responses.Admin.StarVariables
@inject HttpApiClient ApiClient
@inject ModalService ModalService
@inject AlertService AlertService
@inject ToastService ToastService
<div class="flex justify-end mb-5">
<button type="button" @onclick="AddVariable" class="btn btn-primary">Add variable</button>
</div>
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="grid sm:grid-cols-2 xl:grid-cols-3 gap-4">
@foreach (var variable in CurrentVariables)
{
<div class="col-span-1 card card-body p-2.5">
<div class="flex items-center justify-between">
<div class="ml-3">
<i class="icon-variable text-xl align-middle mr-2"></i>
<span class="align-middle text-lg">@variable.Name</span>
</div>
<div class="gap-x-2">
<button type="button" @onclick="() => UpdateVariable(variable)" class="btn btn-primary">
<i class="icon-settings text-base"></i>
</button>
<button type="button" @onclick="() => DeleteVariable(variable)" class="btn btn-error">
<i class="icon-trash text-base"></i>
</button>
</div>
</div>
</div>
}
</div>
</LazyLoader>
@code
{
[Parameter] public StarDetailResponse Star { get; set; }
private StarVariableDetailResponse[] CurrentVariables;
private LazyLoader LazyLoader;
private async Task Load(LazyLoader arg)
{
var pagedVariables = await ApiClient.GetJson<PagedData<StarVariableDetailResponse>>(
$"api/admin/servers/stars/{Star.Id}/variables?page=0&pageSize=50"
);
CurrentVariables = pagedVariables.Items;
}
private async Task AddVariable()
{
Func<CreateStarVariableRequest, Task> onSubmit = async request =>
{
await ApiClient.Post($"api/admin/servers/stars/{Star.Id}/variables", request);
await ToastService.Success("Successfully created variable");
await LazyLoader.Reload();
};
await ModalService.Launch<CreateVariableModal>(parameters =>
{
parameters.Add("OnSubmit", onSubmit);
}, "max-w-xl");
}
private async Task UpdateVariable(StarVariableDetailResponse variable)
{
Func<UpdateStarVariableRequest, Task> onSubmit = async request =>
{
await ApiClient.Patch($"api/admin/servers/stars/{Star.Id}/variables/{variable.Id}", request);
await ToastService.Success("Successfully updated variable");
await LazyLoader.Reload();
};
await ModalService.Launch<UpdateVariableModal>(parameters =>
{
parameters.Add("OnSubmit", onSubmit);
parameters.Add("Variable", variable);
}, "max-w-xl");
}
private async Task DeleteVariable(StarVariableDetailResponse variable)
{
await AlertService.ConfirmDanger(
"Delete variable",
"Do you really want to delete the selected variable? This cannot be undone",
async () =>
{
await ApiClient.Delete($"api/admin/servers/stars/{Star.Id}/variables/{variable.Id}");
await ToastService.Success("Successfully deleted variable");
await LazyLoader.Reload();
}
);
}
}