Merge pull request #156 from Moonlight-Panel/AddServerBackgroundImage

Add dynamic background images for servers
This commit is contained in:
Marcel Baumgartner 2023-06-11 16:28:03 +02:00 committed by GitHub
commit 6591bbc927
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1526 additions and 334 deletions

View file

@ -20,4 +20,5 @@ public class Image
public List<DockerImage> DockerImages { get; set; } = new();
public List<ImageVariable> Variables { get; set; } = new();
public string TagsJson { get; set; } = "";
public string BackgroundImageUrl { get; set; } = "";
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddBackgroundImageUrlImage : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "BackgroundImageUrl",
table: "Images",
type: "longtext",
nullable: false)
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "BackgroundImageUrl",
table: "Images");
}
}
}

View file

@ -132,6 +132,10 @@ namespace Moonlight.App.Database.Migrations
b.Property<int>("Allocations")
.HasColumnType("int");
b.Property<string>("BackgroundImageUrl")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ConfigFiles")
.IsRequired()
.HasColumnType("longtext");

View file

@ -46,6 +46,29 @@ public class ResourcesController : Controller
return NotFound();
}
[HttpGet("background/{name}")]
public async Task<ActionResult> GetBackground([FromRoute] string name)
{
if (name.Contains(".."))
{
await SecurityLogService.Log(SecurityLogType.PathTransversal, x =>
{
x.Add<string>(name);
});
return NotFound();
}
if (System.IO.File.Exists(PathBuilder.File("storage", "resources", "public", "background", name)))
{
var fs = new FileStream(PathBuilder.File("storage", "resources", "public", "background", name), FileMode.Open);
return File(fs, MimeTypes.GetMimeType(name), name);
}
return NotFound();
}
[HttpGet("bucket/{bucket}/{name}")]
public async Task<ActionResult> GetBucket([FromRoute] string bucket, [FromRoute] string name)

View file

@ -15,6 +15,11 @@ public class ResourceService
{
return $"{AppUrl}/api/moonlight/resources/images/{name}";
}
public string BackgroundImage(string name)
{
return $"{AppUrl}/api/moonlight/resources/background/{name}";
}
public string Avatar(User user)
{

View file

@ -0,0 +1,39 @@
using Logging.Net;
using Moonlight.App.Services.Files;
namespace Moonlight.App.Services.Sessions;
public class DynamicBackgroundService
{
public EventHandler OnBackgroundImageChanged { get; set; }
public string BackgroundImageUrl { get; private set; }
private string DefaultBackgroundImageUrl;
public DynamicBackgroundService(ResourceService resourceService)
{
DefaultBackgroundImageUrl = resourceService.BackgroundImage("main.jpg");
BackgroundImageUrl = DefaultBackgroundImageUrl;
}
public Task Change(string url)
{
if(BackgroundImageUrl == url) // Prevent unnecessary updates
return Task.CompletedTask;
BackgroundImageUrl = url;
OnBackgroundImageChanged?.Invoke(this, null!);
return Task.CompletedTask;
}
public Task Reset()
{
if(BackgroundImageUrl == DefaultBackgroundImageUrl) // Prevent unnecessary updates
return Task.CompletedTask;
BackgroundImageUrl = DefaultBackgroundImageUrl;
OnBackgroundImageChanged?.Invoke(this, null!);
return Task.CompletedTask;
}
}

View file

@ -74,6 +74,7 @@
<Folder Include="App\ApiClients\CloudPanel\Resources\" />
<Folder Include="App\Http\Middleware" />
<Folder Include="storage\backups\" />
<Folder Include="storage\resources\public\background\" />
</ItemGroup>
</Project>

View file

@ -134,6 +134,7 @@ namespace Moonlight
builder.Services.AddScoped<ReCaptchaService>();
builder.Services.AddScoped<IpBanService>();
builder.Services.AddSingleton<OAuth2Service>();
builder.Services.AddScoped<DynamicBackgroundService>();
builder.Services.AddScoped<SubscriptionService>();
builder.Services.AddScoped<SubscriptionAdminService>();

View file

@ -20,6 +20,7 @@
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService
@inject IpBanService IpBanService
@inject DynamicBackgroundService DynamicBackgroundService
<GlobalErrorBoundary>
@{
@ -56,7 +57,7 @@
<Sidebar></Sidebar>
<div class="app-main flex-column flex-row-fluid" id="kt_app_main">
<div class="d-flex flex-column flex-column-fluid">
<div id="kt_app_content" class="app-content flex-column-fluid">
<div id="kt_app_content" class="app-content flex-column-fluid" style="background-position: center; background-size: cover; background-repeat: no-repeat; background-attachment: fixed; background-image: url('@(DynamicBackgroundService.BackgroundImageUrl)')">
<div id="kt_app_content_container" class="app-container container-fluid">
<div class="mt-10">
<SoftErrorBoundary>
@ -189,6 +190,11 @@
{
try
{
DynamicBackgroundService.OnBackgroundImageChanged += async (_, _) =>
{
await InvokeAsync(StateHasChanged);
};
IsIpBanned = await IpBanService.IsBanned();
if(IsIpBanned)
@ -211,7 +217,13 @@
await SessionService.Register();
NavigationManager.LocationChanged += (sender, args) => { SessionService.Refresh(); };
NavigationManager.LocationChanged += async (_, _) =>
{
SessionService.Refresh();
if (!NavigationManager.Uri.Contains("/server/"))
await DynamicBackgroundService.Reset();
};
if (User != null)
{

View file

@ -13,240 +13,250 @@
@inject FileDownloadService FileDownloadService
<OnlyAdmin>
<div class="row">
<LazyLoader @ref="LazyLoader" Load="Load">
@if (Image == null)
{
<div class="alert alert-danger">
<TL>No image with this id found</TL>
</div>
}
else
{
<div class="row">
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<div class="mb-10">
<label class="form-label">
<TL>Name</TL>
</label>
<input @bind="Image.Name" type="text" class="form-control">
</div>
<div class="mb-10">
<label class="form-label">
<TL>Description</TL>
</label>
<textarea @bind="Image.Description" type="text" class="form-control"></textarea>
</div>
</div>
</div>
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<label class="form-label">
<TL>Tags</TL>
</label>
<div class="input-group mb-5">
<input @bind="AddTagName" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter tag name"))">
<button @onclick="AddTag" class="btn btn-primary">
<TL>Add</TL>
</button>
</div>
<div>
@if (Tags.Any())
{
<div class="row">
@foreach (var tag in Tags)
{
<button @onclick="() => RemoveTag(tag)" class="col m-3 btn btn-outline-primary mw-25">
@(tag)
</button>
}
</div>
}
else
{
<div class="alert alert-primary">
<TL>No tags found</TL>
</div>
}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<label class="form-label">
<TL>Docker images</TL>
</label>
<div class="input-group mb-5">
<input @bind="NewDockerImage.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter docker image name"))">
<button @onclick="AddDockerImage" class="btn btn-primary">
<TL>Add</TL>
</button>
</div>
<div>
@if (Image.DockerImages.Any())
{
<div class="row">
@foreach (var imageDocker in Image.DockerImages)
{
<button @onclick="() => RemoveDockerImage(imageDocker)" class="col m-3 btn btn-outline-primary mw-25">
@(imageDocker.Name)
</button>
}
</div>
}
else
{
<div class="alert alert-primary">
<TL>No docker images found</TL>
</div>
}
</div>
</div>
</div>
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<div class="mb-10">
<label class="form-label">
<TL>Default image</TL>
</label>
<select @bind="DefaultImageIndex" class="form-select">
@foreach (var image in Image.DockerImages)
{
<option value="@(image.Id)">@(image.Name)</option>
}
</select>
</div>
<div class="mb-10">
<label class="form-label">
<TL>Allocations</TL>
</label>
<input @bind="Image.Allocations" type="number" class="form-control">
</div>
</div>
</div>
</div>
<div class="row mx-0">
<div class="card card-body">
<div class="mb-10">
<label class="form-label">
<TL>Startup command</TL>
</label>
<input @bind="Image.Startup" type="text" class="form-control">
</div>
<div class="row">
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="mb-10">
<label class="form-label">
<TL>Install container</TL>
</label>
<input @bind="Image.InstallDockerImage" type="text" class="form-control">
</div>
</div>
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="mb-10">
<label class="form-label">
<TL>Install entry</TL>
</label>
<input @bind="Image.InstallEntrypoint" type="text" class="form-control">
</div>
</div>
</div>
<div class="card card-flush">
<FileEditor @ref="Editor" Language="shell" InitialData="@(Image.InstallScript)" HideControls="true"/>
</div>
</div>
</div>
<div class="row my-8">
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<div class="mb-10">
<label class="form-label">
<TL>Configuration files</TL>
</label>
<textarea @bind="Image.ConfigFiles" class="form-control"></textarea>
</div>
</div>
</div>
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<div class="mb-10">
<label class="form-label">
<TL>Startup detection</TL>
</label>
<input @bind="Image.StartupDetection" type="text" class="form-control">
</div>
<div class="mb-10">
<label class="form-label">
<TL>Stop command</TL>
</label>
<input @bind="Image.StopCommand" type="text" class="form-control">
</div>
</div>
</div>
</div>
<div class="row my-6">
<div class="card card-body">
<div class="input-group mb-5">
<input type="text" @bind="ImageVariable.Key" placeholder="@(SmartTranslateService.Translate("Key"))" class="form-control">
<input type="text" @bind="ImageVariable.DefaultValue" placeholder="@(SmartTranslateService.Translate("Default value"))" class="form-control">
<button @onclick="AddVariable" class="btn btn-primary">
<TL>Add</TL>
</button>
</div>
<div>
@if (Image!.Variables.Any())
{
<div class="row">
@foreach (var variable in Image!.Variables)
{
<div class="input-group mb-3">
<input type="text" @bind="variable.Key" placeholder="@(SmartTranslateService.Translate("Key"))" class="form-control">
<input type="text" @bind="variable.DefaultValue" placeholder="@(SmartTranslateService.Translate("Default value"))" class="form-control">
<button @onclick="() => RemoveVariable(variable)" class="btn btn-danger">
<TL>Remove</TL>
</button>
</div>
}
</div>
}
else
{
<div class="alert alert-primary">
<TL>No variables found</TL>
</div>
}
</div>
</div>
</div>
<div class="row">
<div class="card card-body">
<div class="d-flex justify-content-end">
<a href="/admin/servers/images" class="btn btn-danger me-3">
<TL>Cancel</TL>
</a>
<WButton Text="@(SmartTranslateService.Translate("Export"))"
WorkingText="@(SmartTranslateService.Translate("Exporting"))"
CssClasses="btn-primary me-3"
OnClick="Export">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Save"))"
WorkingText="@(SmartTranslateService.Translate("Saving"))"
CssClasses="btn-success"
OnClick="Save">
</WButton>
</div>
</div>
</div>
}
</LazyLoader>
<div class="row">
<LazyLoader @ref="LazyLoader" Load="Load">
@if (Image == null)
{
<div class="alert alert-danger">
<TL>No image with this id found</TL>
</div>
}
else
{
<div class="row">
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<div class="mb-10">
<label class="form-label">
<TL>Name</TL>
</label>
<input @bind="Image.Name" type="text" class="form-control">
</div>
<div class="mb-10">
<label class="form-label">
<TL>Description</TL>
</label>
<textarea @bind="Image.Description" type="text" class="form-control"></textarea>
</div>
<div class="mb-10">
<label class="form-label">
<TL>Background image url</TL>
</label>
<input
@bind="Image.BackgroundImageUrl"
type="text"
class="form-control"
placeholder="@(SmartTranslateService.Translate("Leave empty for the default background image"))">
</div>
</div>
</div>
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<label class="form-label">
<TL>Tags</TL>
</label>
<div class="input-group mb-5">
<input @bind="AddTagName" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter tag name"))">
<button @onclick="AddTag" class="btn btn-primary">
<TL>Add</TL>
</button>
</div>
<div>
@if (Tags.Any())
{
<div class="row">
@foreach (var tag in Tags)
{
<button @onclick="() => RemoveTag(tag)" class="col m-3 btn btn-outline-primary mw-25">
@(tag)
</button>
}
</div>
}
else
{
<div class="alert alert-primary">
<TL>No tags found</TL>
</div>
}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<label class="form-label">
<TL>Docker images</TL>
</label>
<div class="input-group mb-5">
<input @bind="NewDockerImage.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter docker image name"))">
<button @onclick="AddDockerImage" class="btn btn-primary">
<TL>Add</TL>
</button>
</div>
<div>
@if (Image.DockerImages.Any())
{
<div class="row">
@foreach (var imageDocker in Image.DockerImages)
{
<button @onclick="() => RemoveDockerImage(imageDocker)" class="col m-3 btn btn-outline-primary mw-25">
@(imageDocker.Name)
</button>
}
</div>
}
else
{
<div class="alert alert-primary">
<TL>No docker images found</TL>
</div>
}
</div>
</div>
</div>
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<div class="mb-10">
<label class="form-label">
<TL>Default image</TL>
</label>
<select @bind="DefaultImageIndex" class="form-select">
@foreach (var image in Image.DockerImages)
{
<option value="@(image.Id)">@(image.Name)</option>
}
</select>
</div>
<div class="mb-10">
<label class="form-label">
<TL>Allocations</TL>
</label>
<input @bind="Image.Allocations" type="number" class="form-control">
</div>
</div>
</div>
</div>
<div class="row mx-0">
<div class="card card-body">
<div class="mb-10">
<label class="form-label">
<TL>Startup command</TL>
</label>
<input @bind="Image.Startup" type="text" class="form-control">
</div>
<div class="row">
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="mb-10">
<label class="form-label">
<TL>Install container</TL>
</label>
<input @bind="Image.InstallDockerImage" type="text" class="form-control">
</div>
</div>
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="mb-10">
<label class="form-label">
<TL>Install entry</TL>
</label>
<input @bind="Image.InstallEntrypoint" type="text" class="form-control">
</div>
</div>
</div>
<div class="card card-flush">
<FileEditor @ref="Editor" Language="shell" InitialData="@(Image.InstallScript)" HideControls="true"/>
</div>
</div>
</div>
<div class="row my-8">
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<div class="mb-10">
<label class="form-label">
<TL>Configuration files</TL>
</label>
<textarea @bind="Image.ConfigFiles" class="form-control"></textarea>
</div>
</div>
</div>
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
<div class="mb-10">
<label class="form-label">
<TL>Startup detection</TL>
</label>
<input @bind="Image.StartupDetection" type="text" class="form-control">
</div>
<div class="mb-10">
<label class="form-label">
<TL>Stop command</TL>
</label>
<input @bind="Image.StopCommand" type="text" class="form-control">
</div>
</div>
</div>
</div>
<div class="row my-6">
<div class="card card-body">
<div class="input-group mb-5">
<input type="text" @bind="ImageVariable.Key" placeholder="@(SmartTranslateService.Translate("Key"))" class="form-control">
<input type="text" @bind="ImageVariable.DefaultValue" placeholder="@(SmartTranslateService.Translate("Default value"))" class="form-control">
<button @onclick="AddVariable" class="btn btn-primary">
<TL>Add</TL>
</button>
</div>
<div>
@if (Image!.Variables.Any())
{
<div class="row">
@foreach (var variable in Image!.Variables)
{
<div class="input-group mb-3">
<input type="text" @bind="variable.Key" placeholder="@(SmartTranslateService.Translate("Key"))" class="form-control">
<input type="text" @bind="variable.DefaultValue" placeholder="@(SmartTranslateService.Translate("Default value"))" class="form-control">
<button @onclick="() => RemoveVariable(variable)" class="btn btn-danger">
<TL>Remove</TL>
</button>
</div>
}
</div>
}
else
{
<div class="alert alert-primary">
<TL>No variables found</TL>
</div>
}
</div>
</div>
</div>
<div class="row">
<div class="card card-body">
<div class="d-flex justify-content-end">
<a href="/admin/servers/images" class="btn btn-danger me-3">
<TL>Cancel</TL>
</a>
<WButton Text="@(SmartTranslateService.Translate("Export"))"
WorkingText="@(SmartTranslateService.Translate("Exporting"))"
CssClasses="btn-primary me-3"
OnClick="Export">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Save"))"
WorkingText="@(SmartTranslateService.Translate("Saving"))"
CssClasses="btn-success"
OnClick="Save">
</WButton>
</div>
</div>
</div>
}
</LazyLoader>
</div>
</OnlyAdmin>
@code
@ -330,7 +340,7 @@
{
Image!.DockerImages.Remove(image);
}
private void AddVariable()
{
Image!.Variables.Add(ImageVariable);
@ -361,7 +371,7 @@
{
Image.TagsJson = JsonConvert.SerializeObject(Tags);
Image.InstallScript = await Editor.GetData();
var json = JsonConvert.SerializeObject(Image, Formatting.Indented);
await FileDownloadService.DownloadString(Image.Name + ".json", json);
}

View file

@ -125,111 +125,115 @@
</div>
</div>
<!--d-flex flex-row mb-5-->
<div class="card mb-5">
<div class="card-header card-header-stretch">
<div class="card-title d-flex align-items-center">
<h3 class="fw-bold m-0 text-gray-800">
<TL>Create something new</TL>
</h3>
</div>
</div>
<div class="card-body pt-3">
<div class="flex-row">
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-server"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/servers/create" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Create a gameserver</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>A new gameserver in just a few minutes</TL>
</span>
<div class="row">
<div class="col">
<div class="card mb-5">
<div class="card-header card-header-stretch">
<div class="card-title d-flex align-items-center">
<h3 class="fw-bold m-0 text-gray-800">
<TL>Create something new</TL>
</h3>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-globe"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/webspaces/create" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Create a webspace</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Make your own websites with a webspace</TL>
</span>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-purchase-tag"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/domains/create" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Create a domain</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Make your servvices accessible throught your own domain</TL>
</span>
<div class="card-body pt-3">
<div class="flex-row">
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-server"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/servers/create" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Create a gameserver</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>A new gameserver in just a few minutes</TL>
</span>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-globe"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/webspaces/create" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Create a webspace</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Make your own websites with a webspace</TL>
</span>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-purchase-tag"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/domains/create" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Create a domain</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Make your services accessible through your own domain</TL>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card mb-5">
<div class="card-header card-header-stretch">
<div class="card-title d-flex align-items-center">
<h3 class="fw-bold m-0 text-gray-800">
<TL>Manage your services</TL>
</h3>
</div>
</div>
<div class="card-body pt-3">
<div class="flex-row">
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-server"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/servers" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Manage your gameservers</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Adjust your gameservers</TL>
</span>
<div class="col">
<div class="card mb-5">
<div class="card-header card-header-stretch">
<div class="card-title d-flex align-items-center">
<h3 class="fw-bold m-0 text-gray-800">
<TL>Manage your services</TL>
</h3>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-globe"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/webspaces" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Manage your webspaces</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Modify the content of your webspaces</TL>
</span>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-purchase-tag"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/domains" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Manage your domains</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Add, edit and delete dns records</TL>
</span>
<div class="card-body pt-3">
<div class="flex-row">
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-server"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/servers" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Manage your gameservers</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Adjust your gameservers</TL>
</span>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-globe"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/webspaces" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Manage your webspaces</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Modify the content of your webspaces</TL>
</span>
</div>
</div>
<div class="separator mb-2 mt-2"></div>
<div class="d-flex align-items-center">
<div class="symbol symbol-50px me-3">
<i class="bx bx-md bx-purchase-tag"></i>
</div>
<div class="d-flex justify-content-start flex-column">
<a href="/domains" class="text-gray-800 text-hover-primary mb-1 fs-5">
<TL>Manage your domains</TL>
</a>
<span class="text-gray-400 fw-semibold d-block fs-6">
<TL>Add, edit and delete dns records</TL>
</span>
</div>
</div>
</div>
</div>
</div>

View file

@ -10,6 +10,7 @@
@using Moonlight.App.Helpers.Wings.Enums
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@using Moonlight.App.Services.Sessions
@using Moonlight.Shared.Components.Xterm
@using Moonlight.Shared.Components.ServerControl
@using Newtonsoft.Json
@ -20,6 +21,7 @@
@inject EventSystem Event
@inject ServerService ServerService
@inject NavigationManager NavigationManager
@inject DynamicBackgroundService DynamicBackgroundService
@implements IDisposable
@ -291,6 +293,11 @@
return Task.CompletedTask;
});
if (string.IsNullOrEmpty(Image.BackgroundImageUrl))
await DynamicBackgroundService.Reset();
else
await DynamicBackgroundService.Change(Image.BackgroundImageUrl);
}
}
else

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB