Removed old project structure

This commit is contained in:
Marcel Baumgartner
2023-10-13 11:06:16 +02:00
parent 84d39c1c75
commit 39b632d483
629 changed files with 0 additions and 148526 deletions

View File

@@ -1,16 +0,0 @@
@using Moonlight.App.Services
@using Moonlight.App.Services.Files
@inject ResourceService ResourceService
<div class="card card-flush w-lg-650px py-5">
<div class="card-body py-15 py-lg-20">
<div class="mb-14">
<img alt="Logo" src="@(ResourceService.Image("logolong.png"))" class="h-40px">
</div>
<h1 class="fw-bolder text-gray-900 mb-5"><TL>Your account is banned from moonlight</TL></h1>
<div class="fw-semibold fs-6 text-gray-500 mb-8">
<TL>Your account is banned from using moonlight. Your data will be deleted shortly</TL>
</div>
</div>
</div>

View File

@@ -1,16 +0,0 @@
@using Moonlight.App.Services
@using Moonlight.App.Services.Files
@inject ResourceService ResourceService
<div class="card card-flush w-lg-650px py-5">
<div class="card-body py-15 py-lg-20">
<div class="mb-14">
<img alt="Logo" src="@(ResourceService.Image("logolong.png"))" class="h-40px">
</div>
<h1 class="fw-bolder text-gray-900 mb-5"><TL>Your moonlight account is disabled</TL></h1>
<div class="fw-semibold fs-6 text-gray-500 mb-8">
<TL>Your moonlight account is currently disabled. But dont worry your data is still saved</TL>
</div>
</div>
</div>

View File

@@ -1,13 +0,0 @@
<div class="mx-auto">
<div class="card">
<div class="d-flex justify-content-center pt-5">
<img height="300" width="300" src="/assets/media/svg/warning.svg" alt="Warning"/>
</div>
<span class="card-title text-center fs-3">
<TL>You have no permission to access this resource</TL>
</span>
<p class="card-body text-center fs-4 text-gray-800">
<TL>You have no permission to access this resource. This attempt has been logged ;)</TL>
</p>
</div>
</div>

View File

@@ -1,31 +0,0 @@
<div class="mx-auto">
<div class="card">
<div class="d-flex justify-content-center pt-5">
<img height="300" width="300" src="/assets/media/svg/notfound.svg" alt="Not found"/>
</div>
<span class="card-title text-center fs-3">
<TL>The requested resource was not found</TL>
</span>
<p class="card-body text-center fs-4 text-gray-800">
<TL>We were not able to find the requested resource. This can have following reasons</TL>
<div class="mt-4 d-flex flex-column align-items-center">
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> <TL>The resource was deleted</TL>
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> <TL>You have no permission to access this resource</TL>
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> <TL>You may have entered invalid data</TL>
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> <TL>A unknown bug occured</TL>
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> <TL>An api was down and not proper handled</TL>
</li>
</div>
</p>
</div>
</div>

View File

@@ -1,193 +0,0 @@
@page "/login"
@* This is just a "virtual" route/page. The handling for that is
@* MainLayout doing for us. We need to put that here so the router
@* does not return the 404 page
*@
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services
@using Moonlight.App.Exceptions
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services.Sessions
@using System.ComponentModel.DataAnnotations
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Forms
@inject AlertService AlertService
@inject UserService UserService
@inject SmartTranslateService SmartTranslateService
@inject CookieService CookieService
@inject NavigationManager NavigationManager
@inject OAuth2Service OAuth2Service
<div class="d-flex flex-center">
<div class="card rounded-3 w-md-550px">
<div class="card-body">
<div class="d-flex flex-center flex-column-fluid pb-15 pb-lg-20">
@if (!TotpRequired)
{
<SmartForm Model="LoginData" OnValidSubmit="DoLogin">
<div class="text-center mt-3 mb-11">
<h1 class="text-dark fw-bolder mb-3">
<TL>Sign In</TL>
</h1>
<div class="text-gray-500 fw-semibold fs-6">
<TL>Sign in to start with moonlight</TL>
</div>
</div>
@foreach (var providers in OAuth2Service.Providers.Chunk(2))
{
<div class="row g-3 mb-9">
@foreach (var provider in providers)
{
<div class="col">
<a href="#" @onclick:preventDefault @onclick="() => StartOAuth2(provider.Key)" class="btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100">
<div class="h-15px me-3">
<i class="mb-1 bx bx-md bx-fingerprint"></i>
</div>
<TL>Sign in with</TL>&nbsp; @(provider.Value.DisplayName)
</a>
</div>
}
</div>
}
@if (OAuth2Service.Providers.Any())
{
<div class="separator separator-content my-14">
<span class="w-125px text-gray-500 fw-semibold fs-7">
<TL>Or with email</TL>
</span>
</div>
}
<div class="mt-3 mb-3">
<InputText @bind-Value="LoginData.Email" type="email" placeholder="@(SmartTranslateService.Translate("Email"))" class="form-control bg-transparent"/>
</div>
<div class="mb-3">
<InputText @bind-Value="LoginData.Password" type="password" placeholder="@(SmartTranslateService.Translate("Password"))" class="form-control bg-transparent"/>
</div>
<div class="d-flex flex-stack flex-wrap gap-3 fs-base fw-semibold mb-8">
<div></div>
<a href="/passwordreset" class="link-primary">
<TL>Forgot password?</TL>
</a>
</div>
<div class="d-grid mb-10">
<button type="submit" class="btn btn-primary">
<TL>Sign-in</TL>
</button>
</div>
<div class="text-gray-500 text-center fw-semibold fs-6">
<TL>Not registered yet?</TL>
<a href="/register" class="link-primary">
<TL>Sign up</TL>
</a>
</div>
</SmartForm>
}
else
{
<SmartForm Model="TotpData" OnValidSubmit="DoLogin">
<div class="fv-row mb-8 fv-plugins-icon-container">
<InputText @bind-Value="TotpData.Code" type="number" class="form-control bg-transparent" placeholder="@(SmartTranslateService.Translate("2fa code"))"></InputText>
</div>
<div class="d-grid mb-10">
<button type="submit" class="btn btn-primary">
<TL>Sign-in</TL>
</button>
</div>
</SmartForm>
}
</div>
</div>
</div>
</div>
@code
{
private LoginDataModel LoginData = new();
private LoginTotpDataModel TotpData = new();
private bool TotpRequired = false;
private async Task DoLogin()
{
try
{
LoginData.Email = LoginData.Email.ToLower().Trim();
if (string.IsNullOrEmpty(TotpData.Code))
{
TotpRequired = await UserService.CheckTotp(LoginData.Email, LoginData.Password);
if (!TotpRequired)
{
var token = await UserService.Login(LoginData.Email, LoginData.Password);
await CookieService.SetValue("token", token, 10);
if (NavigationManager.Uri.EndsWith("login"))
NavigationManager.NavigateTo("/", true);
else
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
else
{
await InvokeAsync(StateHasChanged);
}
}
else
{
var token = await UserService.Login(LoginData.Email, LoginData.Password, TotpData.Code);
await CookieService.SetValue("token", token, 10);
if (NavigationManager.Uri.EndsWith("login"))
NavigationManager.NavigateTo("/", true);
else
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}
catch (DisplayException e)
{
// Reset state
LoginData = new();
TotpData = new();
TotpRequired = false;
await AlertService.Error(
SmartTranslateService.Translate("Error"),
SmartTranslateService.Translate(e.Message)
);
}
catch (Exception e)
{
Logger.Error("Error while login");
Logger.Error(e);
// Reset state
LoginData = new();
TotpData = new();
TotpRequired = false;
await AlertService.Error(
SmartTranslateService.Translate("Error"),
SmartTranslateService.Translate("An error occured while logging you in")
);
}
}
private async Task StartOAuth2(string id)
{
var url = await OAuth2Service.GetUrl(id);
NavigationManager.NavigateTo(url ,true);
}
}

View File

@@ -1,65 +0,0 @@
@using Moonlight.App.Services
@using Moonlight.App.Models.Forms
@using Moonlight.App.Services.Sessions
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc
@using Moonlight.App.Repositories
@inject SmartTranslateService SmartTranslateService
@inject IdentityService IdentityService
@inject UserService UserService
@inject UserRepository UserRepository
@inject NavigationManager NavigationManager
<div class="d-flex flex-center">
<div class="card rounded-3 w-md-550px">
<div class="card-body">
<div class="d-flex flex-center flex-column-fluid">
<LazyLoader Load="Load">
<SmartForm Model="Password" OnValidSubmit="DoChange">
<div class="text-center mt-3 mb-11">
<h1 class="text-dark fw-bolder mb-3">
<TL>Change your password</TL>
</h1>
<div class="text-gray-500 fw-semibold fs-6">
<TL>You need to change your password in order to use moonlight</TL>
</div>
</div>
<div class="row g-3 mb-9">
<div class="col-md-9">
<InputText @bind-Value="Password.Password" type="password" placeholder="@(SmartTranslateService.Translate("New password"))" class="form-control bg-transparent"/>
</div>
<div class="col">
<button type="submit" class="btn btn-primary float-end">
<TL>Change</TL>
</button>
</div>
</div>
</SmartForm>
</LazyLoader>
</div>
</div>
</div>
</div>
@code {
private PasswordModel Password = new();
private User User;
private Task Load(LazyLoader loader)
{
User = IdentityService.User;
return Task.CompletedTask;
}
private async Task DoChange()
{
await UserService.ChangePassword(User, Password.Password);
User.Status = UserStatus.Unverified;
UserRepository.Update(User);
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}

View File

@@ -1,73 +0,0 @@
@page "/passwordreset"
@using Moonlight.App.Services
@* This is just a "virtual" route/page. The handling for that is
@* MainLayout doing for us. We need to put that here so the router
@* does not return the 404 page
*@
@inject UserService UserService
@inject SmartTranslateService SmartTranslateService
<div class="d-flex flex-center">
<div class="card rounded-3 w-md-550px">
<div class="card-body">
<div class="d-flex flex-center flex-column-fluid pb-15 pb-lg-20">
@if (Send)
{
<div class="text-center mb-11">
<h1 class="text-dark fw-bolder mb-3">
<TL>Passwort reset successfull. Check your mail</TL>
</h1>
</div>
}
else
{
<div class="form w-100 fv-plugins-bootstrap5 fv-plugins-framework" novalidate="novalidate">
<div class="text-center mb-11">
<h1 class="text-dark fw-bolder mb-3">
<TL>Password reset</TL>
</h1>
<div class="text-gray-500 fw-semibold fs-6">
<TL>Reset the password of your account</TL>
</div>
</div>
<div class="fv-row mb-8 fv-plugins-icon-container">
<input @bind="Email" type="email" placeholder="@(SmartTranslateService.Translate("Email"))" name="email" class="form-control bg-transparent">
</div>
<div class="d-grid mb-10">
<WButton Text="@(SmartTranslateService.Translate("Reset password"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn-primary"
OnClick="Submit">
</WButton>
</div>
<div class="text-gray-500 text-center fw-semibold fs-6">
<TL>Wrong here?</TL>
<a href="/login" class="link-primary">
<TL>Sign in</TL>
</a>
</div>
</div>
}
</div>
</div>
</div>
</div>
@code
{
private string Email = "";
private bool Send = false;
private async Task Submit()
{
await UserService.ResetPassword(Email);
Send = true;
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,136 +0,0 @@
@page "/register"
@* This is just a "virtual" route/page. The handling for that is
@* MainLayout doing for us. We need to put that here so the router
@* does not return the 404 page
*@
@using Moonlight.App.Services
@using Moonlight.App.Models.Forms
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions
@inject SmartTranslateService SmartTranslateService
@inject NavigationManager NavigationManager
@inject AlertService AlertService
@inject UserService UserService
@inject CookieService CookieService
@inject OAuth2Service OAuth2Service
<div class="d-flex flex-center">
<div class="card rounded-3 w-md-550px">
<div class="card-body">
<div class="d-flex flex-center flex-column-fluid pb-15 pb-lg-20">
<div class="form w-100 fv-plugins-bootstrap5 fv-plugins-framework" novalidate="novalidate">
<div class="text-center mb-11">
<h1 class="text-dark fw-bolder mb-3">
<TL>Sign Up</TL>
</h1>
<div class="text-gray-500 fw-semibold fs-6">
<TL>Sign up to start with moonlight</TL>
</div>
</div>
@foreach (var providers in OAuth2Service.Providers.Chunk(2))
{
<div class="row g-3 mb-9">
@foreach (var provider in providers)
{
<div class="col-md-6">
<a href="#" @onclick:preventDefault @onclick="() => StartOAuth2(provider.Key)" class="btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100">
<div class="h-15px me-3">
<i class="mb-1 bx bx-md bx-fingerprint"></i>
</div>
<TL>Sign in with</TL>&nbsp; @(provider.Value.DisplayName)
</a>
</div>
}
</div>
}
@if (OAuth2Service.Providers.Any())
{
<div class="separator separator-content my-10">
<span class="w-125px text-gray-500 fw-semibold fs-7">
<TL>Or with email</TL>
</span>
</div>
}
<SmartForm Model="UserRegisterModel" OnValidSubmit="CreateUser">
<div class="fv-row mb-4 fv-plugins-icon-container">
<InputText @bind-Value="UserRegisterModel.Email" placeholder="@(SmartTranslateService.Translate("Email"))" name="email" autocomplete="off" class="form-control bg-transparent"/>
</div>
<div class="row">
<div class="col-lg-6 mb-4 fv-plugins-icon-container">
<InputText @bind-Value="UserRegisterModel.FirstName" type="text" placeholder="@(SmartTranslateService.Translate("Firstname"))" name="text" class="form-control bg-transparent"/>
</div>
<div class="col-lg-6 mb-4 fv-plugins-icon-container">
<InputText @bind-Value="UserRegisterModel.LastName" type="text" placeholder="@(SmartTranslateService.Translate("Lastname"))" name="text"class="form-control bg-transparent"/>
</div>
</div>
<div class="row">
<div class="col-lg-6 mb-4 fv-plugins-icon-container">
<InputText @bind-Value="UserRegisterModel.Password" type="password" placeholder="@(SmartTranslateService.Translate("Password"))" name="password" autocomplete="off" class="form-control bg-transparent"/>
</div>
<div class="col-lg-6 mb-4 fv-plugins-icon-container">
<InputText @bind-Value="UserRegisterModel.ConfirmPassword" type="password" placeholder="@(SmartTranslateService.Translate("Repeat password"))" name="password" autocomplete="off" class="form-control bg-transparent"/>
</div>
</div>
<div class="row mt-3 mb-3">
<SmartReCaptcha @bind-Value="UserRegisterModel.Captcha">
</SmartReCaptcha>
</div>
<div class="d-grid mb-6">
<button type="submit" class="btn btn-primary">
<TL>Sign-up</TL>
</button>
</div>
</SmartForm>
<div class="text-gray-500 text-center fw-semibold fs-6">
<TL>Already registered?</TL>
<a href="/login" class="link-primary">
<TL>Sign in</TL>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
@code
{
private UserRegisterModel UserRegisterModel = new();
private async Task StartOAuth2(string id)
{
var url = await OAuth2Service.GetUrl(id);
NavigationManager.NavigateTo(url ,true);
}
private async Task CreateUser()
{
if (UserRegisterModel.ConfirmPassword != UserRegisterModel.Password)
{
await AlertService.Error(SmartTranslateService.Translate("Passwords need to match"));
return;
}
var token = await UserService.Register(UserRegisterModel.Email, UserRegisterModel.Password, UserRegisterModel.FirstName, UserRegisterModel.LastName);
await CookieService.SetValue("token", token, 10);
if (NavigationManager.Uri.EndsWith("register"))
NavigationManager.NavigateTo("/", true);
else
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}

View File

@@ -1,68 +0,0 @@
@using Microsoft.AspNetCore.Components
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Forms
@using Moonlight.App.Models.Misc
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@using Moonlight.App.Services.Sessions
@inject IdentityService IdentityService
@inject UserRepository UserRepository
@inject SmartTranslateService SmartTranslateService
@inject NavigationManager NavigationManager
<div class="d-flex flex-center">
<div class="card rounded-3 w-md-550px">
<div class="card-body">
<div class="d-flex flex-center flex-column-fluid">
<LazyLoader Load="Load">
<SmartForm Model="Name" OnValidSubmit="SetName">
<div class="text-center mt-3 mb-11">
<h1 class="text-dark fw-bolder mb-3">
<TL>Enter your information</TL>
</h1>
<div class="text-gray-500 fw-semibold fs-6">
<TL>You need to enter your full name in order to use moonlight</TL>
</div>
</div>
<div class="row g-3">
<div class="col">
<InputText @bind-Value="Name.FirstName" type="text" placeholder="@(SmartTranslateService.Translate("First name"))" class="form-control bg-transparent"/>
</div>
<div class="col">
<InputText @bind-Value="Name.LastName" type="text" placeholder="@(SmartTranslateService.Translate("Last name"))" class="form-control bg-transparent"/>
</div>
</div>
<button type="submit" class="btn btn-primary float-end mt-3">
<TL>Change</TL>
</button>
</SmartForm>
</LazyLoader>
</div>
</div>
</div>
</div>
@code {
private User User;
private NameModel Name = new ();
private Task Load(LazyLoader loader)
{
User = IdentityService.User;
return Task.CompletedTask;
}
private async Task SetName()
{
User.FirstName = Name.FirstName;
User.LastName = Name.LastName;
User.Status = UserStatus.Unverified;
UserRepository.Update(User);
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}

View File

@@ -1,66 +0,0 @@
@using Moonlight.App.Services.Sessions
@using Moonlight.App.Helpers
@inherits ErrorBoundary
@inject IdentityService IdentityService
@if (CurrentException is null)
{
@ChildContent
}
else if (ErrorContent is not null)
{
<div class="card card-flush h-md-100">
<div class="card-body d-flex flex-column justify-content-between mt-9 bgi-no-repeat bgi-size-cover bgi-position-x-center pb-0">
<div class="mb-10">
<div class="fs-2hx fw-bold text-gray-800 text-center mb-13">
<span class="me-2">
<TL>Ooops. Your moonlight client is crashed</TL>
</span>
</div>
<div class="text-center">
<TL>This error has been reported to the moonlight team</TL>
</div>
</div>
</div>
</div>
}
else
{
<div class="card card-flush h-md-100">
<div class="card-body d-flex flex-column justify-content-between mt-9 bgi-no-repeat bgi-size-cover bgi-position-x-center pb-0">
<div class="mb-10">
<div class="fs-2hx fw-bold text-gray-800 text-center mb-13">
<span class="me-2">
<TL>Ooops. Your moonlight client is crashed</TL>
</span>
</div>
<div class="text-center">
<TL>This error has been reported to the moonlight team</TL>
</div>
</div>
</div>
</div>
}
@code
{
List<Exception> receivedExceptions = new();
protected override async Task OnErrorAsync(Exception exception)
{
receivedExceptions.Add(exception);
Logger.Error($"An unhanded exception occured:");
Logger.Error(exception);
await base.OnErrorAsync(exception);
}
public new void Recover()
{
receivedExceptions.Clear();
base.Recover();
}
}

View File

@@ -1,89 +0,0 @@
@using Moonlight.App.Exceptions
@using Moonlight.App.Helpers
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions
@inherits ErrorBoundary
@inject IdentityService IdentityService
@inject AlertService AlertService
@inject SmartTranslateService SmartTranslateService
@if (CurrentException is null)
{
@ChildContent
}
else if (ErrorContent is not null)
{
<div class="card card-flush h-md-100">
<div class="card-body d-flex flex-column justify-content-between mt-9 bgi-no-repeat bgi-size-cover bgi-position-x-center pb-0">
<div class="mb-10">
<div class="fs-2hx fw-bold text-gray-800 text-center mb-13">
<span class="me-2">
<TL>Ooops. This page is crashed</TL>
</span>
</div>
<div class="text-center">
<TL>This page is crashed. The error has been reported to the moonlight team. Meanwhile you can try reloading the page</TL>
</div>
</div>
</div>
</div>
}
else
{
<div class="card card-flush h-md-100">
<div class="card-body d-flex flex-column justify-content-between mt-9 bgi-no-repeat bgi-size-cover bgi-position-x-center pb-0">
<div class="mb-10">
<div class="fs-2hx fw-bold text-gray-800 text-center mb-13">
<span class="me-2">
<TL>Ooops. This page is crashed</TL>
</span>
</div>
<div class="text-center">
<TL>This page is crashed. The error has been reported to the moonlight team. Meanwhile you can try reloading the page</TL>
</div>
</div>
</div>
</div>
}
@code
{
List<Exception> receivedExceptions = new();
protected override async Task OnErrorAsync(Exception exception)
{
receivedExceptions.Add(exception);
var user = IdentityService.User;
var id = user == null ? -1 : user.Id;
Logger.Error($"[{id}] An unhanded exception occured:");
Logger.Error(exception);
await base.OnErrorAsync(exception);
if (exception is DisplayException displayException)
{
Task.Run(async () =>
{
await AlertService.Error(
SmartTranslateService.Translate("Error"),
SmartTranslateService.Translate(displayException.Message)
);
});
Recover();
await InvokeAsync(StateHasChanged);
}
}
public new void Recover()
{
receivedExceptions.Clear();
base.Recover();
}
}

View File

@@ -1,132 +0,0 @@
@using Moonlight.App.Services.Interop
@using Moonlight.App.Exceptions
@using Moonlight.App.Services
@using Moonlight.App.ApiClients.CloudPanel
@using Moonlight.App.ApiClients.Daemon
@using Moonlight.App.ApiClients.Modrinth
@using Moonlight.App.ApiClients.Wings
@using Moonlight.App.Helpers
@using Stripe
@inherits ErrorBoundaryBase
@inject AlertService AlertService
@inject ConfigService ConfigService
@inject SmartTranslateService SmartTranslateService
@inject NavigationManager NavigationManager
@if (HardCrashed)
{
<div class="card card-flush h-md-100">
<div class="card-body d-flex flex-column justify-content-between mt-9 bgi-no-repeat bgi-size-cover bgi-position-x-center pb-0">
<div class="mb-10">
<div class="fs-2hx fw-bold text-gray-800 text-center mb-13">
<span class="me-2">
<TL>Ooops. This page is crashed</TL>
</span>
</div>
<div class="text-center">
<TL>This page is crashed. The error has been reported to the moonlight team. Meanwhile you can try reloading the page</TL>
</div>
</div>
</div>
</div>
}
else if (SoftCrashed)
{
<div class="card card-body bg-danger mb-5">
<span class="text-center">
@(ErrorMessage)
</span>
</div>
@ChildContent
}
else
{
@ChildContent
}
@code
{
private bool HardCrashed = false;
private bool SoftCrashed = false;
private string ErrorMessage = "";
protected override void OnInitialized()
{
NavigationManager.LocationChanged += OnPathChanged;
}
private void OnPathChanged(object? sender, LocationChangedEventArgs e)
{
if (SoftCrashed)
SoftCrashed = false;
StateHasChanged();
}
protected override async Task OnErrorAsync(Exception exception)
{
if (ConfigService.DebugMode)
{
Logger.Verbose(exception);
}
if (exception is DisplayException displayException)
{
if (displayException.DoNotTranslate)
{
await SoftCrash(displayException.Message);
}
else
{
await SoftCrash(SmartTranslateService.Translate(displayException.Message));
}
}
else if (exception is CloudflareException cloudflareException)
{
await SoftCrash(SmartTranslateService.Translate("Error from cloudflare: ") + cloudflareException.Message);
}
else if (exception is WingsException wingsException)
{
await SoftCrash(SmartTranslateService.Translate("Error from wings: ") + wingsException.Message);
Logger.Warn($"Wings exception status code: {wingsException.StatusCode}");
}
else if (exception is DaemonException daemonException)
{
await SoftCrash(SmartTranslateService.Translate("Error from daemon: ") + daemonException.Message);
Logger.Warn($"Wings exception status code: {daemonException.StatusCode}");
}
else if (exception is ModrinthException modrinthException)
{
await SoftCrash(SmartTranslateService.Translate("Error from modrinth: ") + modrinthException.Message);
}
else if (exception is CloudPanelException cloudPanelException)
{
await SoftCrash(SmartTranslateService.Translate("Error from cloudpanel: ") + cloudPanelException.Message);
}
else if (exception is NotImplementedException)
{
await SoftCrash(SmartTranslateService.Translate("This function is not implemented"));
}
else if (exception is StripeException stripeException)
{
await SoftCrash(SmartTranslateService.Translate("Error from stripe: ") + stripeException.Message);
}
else
{
Logger.Warn(exception);
HardCrashed = true;
await InvokeAsync(StateHasChanged);
}
}
private Task SoftCrash(string message)
{
SoftCrashed = true;
ErrorMessage = message;
return Task.CompletedTask;
}
}

View File

@@ -1,87 +0,0 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services.Interop
@inject ModalService ModalService
<div class="modal fade" id="connectionDetails" tabindex="-1" aria-labelledby="connectionDetails" style="display: none;" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<TL>Connection details</TL>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Host</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Host)">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Port</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Port)">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Username</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Username)">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<TL>Close</TL>
</button>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public FileAccess Access { get; set; }
private string Host = "";
private string Username = "";
private int Port;
protected override async Task OnParametersSetAsync()
{
try
{
Uri uri = new Uri(await Access.GetLaunchUrl());
Host = uri.Host;
Port = uri.Port;
Username = uri.UserInfo.Split(':')[0];
}
catch (NotImplementedException)
{
Host = "N/A";
Port = -1;
Username = "N/A";
}
}
public async Task Show()
{
await ModalService.Show("connectionDetails");
}
}

View File

@@ -1,148 +0,0 @@
@using BlazorMonaco
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions
@using Moonlight.Shared.Components.Partials
@inject SmartTranslateService TranslationService
@inject KeyListenerService KeyListenerService
@inject IJSRuntime JsRuntime
@implements IDisposable
<div class="card bg-black rounded">
@if (ShowHeader)
{
<div class="card-header">
<span class="card-title">@(Header)</span>
</div>
}
<div class="card-body">
<MonacoEditor CssClass="h-100" @ref="Editor" Id="vseditor" ConstructionOptions="(x) => EditorOptions"/>
</div>
@if (!HideControls)
{
<div class="card-footer">
<div class="btn-group">
<WButton
Text="@(TranslationService.Translate("Save"))"
WorkingText="@(TranslationService.Translate("Saving"))"
OnClick="Submit"></WButton>
<WButton
CssClasses="btn-danger"
Text="@(TranslationService.Translate("Cancel"))"
WorkingText="@(TranslationService.Translate("Canceling"))"
OnClick="Cancel"></WButton>
</div>
</div>
}
</div>
@code
{
[Parameter]
public string InitialData { get; set; }
[Parameter]
public string Language { get; set; }
[Parameter]
public bool HideControls { get; set; } = false;
[Parameter]
public bool ShowHeader { get; set; } = false;
[Parameter]
public string Header { get; set; } = "Header.changeme.txt";
// Events
[Parameter]
public Action<string> OnSubmit { get; set; }
[Parameter]
public Action OnCancel { get; set; }
// Monaco Editor
private MonacoEditor Editor;
private StandaloneEditorConstructionOptions EditorOptions;
protected override void OnInitialized()
{
EditorOptions = new()
{
AutomaticLayout = true,
Language = "plaintext",
Value = "Loading content",
Theme = "moonlight-theme",
Contextmenu = false,
Minimap = new()
{
Enabled = false
},
AutoIndent = true
};
KeyListenerService.KeyPressed += KeyPressed;
}
private async void KeyPressed(object? sender, string e)
{
if (e == "saveShortcut")
{
await Submit();
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JsRuntime.InvokeVoidAsync("moonlight.loading.loadMonaco");
Editor.OnDidInit = new EventCallback<MonacoEditorBase>(this, async () =>
{
EditorOptions.Language = Language;
var model = await Editor.GetModel();
await MonacoEditorBase.SetModelLanguage(model, EditorOptions.Language);
await Editor.SetPosition(new Position()
{
Column = 0,
LineNumber = 1
});
await Editor.SetValue(InitialData);
await Editor.Layout(new Dimension()
{
Height = 500,
Width = 1000
});
});
}
}
private async Task Submit()
{
var data = await Editor.GetValue();
await InvokeAsync(() => OnSubmit?.Invoke(data));
}
private async Task Cancel()
{
await InvokeAsync(() => OnCancel?.Invoke());
}
public async Task<string> GetData()
{
return await Editor.GetValue();
}
public void Dispose()
{
Editor.Dispose();
KeyListenerService.KeyPressed -= KeyPressed;
}
}

View File

@@ -1,379 +0,0 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Helpers
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using BlazorDownloadFile
@inject ToastService ToastService
@inject NavigationManager NavigationManager
@inject AlertService AlertService
@inject SmartTranslateService SmartTranslateService
@inject IBlazorDownloadFileService FileService
@if (Editing)
{
<FileEditor @ref="Editor"
InitialData="@EditorInitialData"
Language="@EditorLanguage"
OnCancel="() => Cancel()"
OnSubmit="(_) => Save()"
HideControls="false"
ShowHeader="true"
Header="@(EditingFile.Name)">
</FileEditor>
}
else
{
<div class="card mb-7">
<div class="card-header border-0 my-2">
<div class="card-title">
<div class="d-flex flex-stack">
<FilePath Access="Access" OnPathChanged="OnComponentStateChanged"/>
</div>
</div>
<div class="card-toolbar">
<div class="d-flex justify-content-end align-items-center">
@if (View != null && View.SelectedFiles.Any())
{
<div class="fw-bold me-5">
<span class="me-2">
@(View.SelectedFiles.Length) <TL>selected</TL>
</span>
</div>
<WButton Text="@(SmartTranslateService.Translate("Move"))"
WorkingText="@(SmartTranslateService.Translate("Moving"))"
CssClasses="btn-primary me-3"
OnClick="StartMoveFiles">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Compress"))"
WorkingText="@(SmartTranslateService.Translate("Compressing"))"
CssClasses="btn-primary me-3"
OnClick="CompressMultiple">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
CssClasses="btn-danger"
OnClick="DeleteMultiple">
</WButton>
}
else
{
<div class="btn-group me-3">
<button type="button" @onclick="Launch" class="btn btn-light-primary">
<TL>Launch WinSCP</TL>
</button>
<button type="button" class="btn btn-light-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item btn" target="_blank" href="https://winscp.net/eng/downloads.php">
<TL>Download WinSCP</TL>
</a>
</li>
<li>
<button class="dropdown-item btn" @onclick="() => ConnectionDetailsModal.Show()">
<TL>Show connection details</TL>
</button>
</li>
</ul>
</div>
<div class="btn-group me-3">
<button type="button" class="btn btn-light-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<TL>New</TL>&nbsp;
</button>
<ul class="dropdown-menu">
<li>
<button @onclick="CreateFile" class="dropdown-item btn">
<TL>New file</TL>
</button>
</li>
<li>
<button @onclick="CreateFolder" class="dropdown-item btn">
<TL>New folder</TL>
</button>
</li>
</ul>
</div>
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged"/>
}
</div>
</div>
</div>
</div>
<div class="card card-body ps-9">
<FileView @ref="View"
Access="Access"
ContextActions="Actions"
OnSelectionChanged="OnSelectionChanged"
OnElementClicked="OnElementClicked"
DisableScrolling="true">
</FileView>
</div>
<FileSelectModal @ref="FileSelectModal"
OnlyFolder="true"
Title="@(SmartTranslateService.Translate("Select folder to move the file(s) to"))"
Access="MoveAccess"
OnSubmit="OnFileMoveSubmit">
</FileSelectModal>
<ConnectionDetailsModal @ref="ConnectionDetailsModal" Access="Access"/>
}
@code
{
[Parameter]
public FileAccess Access { get; set; }
// File Editor
private bool Editing = false;
private string EditorInitialData = "";
private string EditorLanguage = "";
private FileData EditingFile;
private FileEditor Editor;
// File View
private FileView? View;
// File Move
private FileAccess MoveAccess;
private FileSelectModal FileSelectModal;
private FileData? SingleMoveFile = null;
// Config
private ContextAction[] Actions = Array.Empty<ContextAction>();
// Connection details
private ConnectionDetailsModal ConnectionDetailsModal;
protected override void OnInitialized()
{
MoveAccess = (FileAccess)Access.Clone();
List<ContextAction> actions = new();
actions.Add(new()
{
Id = "rename",
Name = "Rename",
Action = async (x) =>
{
var name = await AlertService.Text(
SmartTranslateService.Translate("Rename"),
SmartTranslateService.Translate("Enter a new name"),
x.Name
);
if (name != x.Name)
{
await Access.Move(x, Access.CurrentPath + name);
}
await View!.Refresh();
}
});
actions.Add(new()
{
Id = "download",
Name = "Download",
Action = async (x) =>
{
if (x.IsFile)
{
try
{
var stream = await Access.DownloadStream(x);
await ToastService.Info(SmartTranslateService.Translate("Starting download"));
stream.Position = 0;
await FileService.DownloadFile(fileName: x.Name, stream: stream, contentType: "application/octet-stream");
}
catch (NotImplementedException)
{
var url = await Access.DownloadUrl(x);
NavigationManager.NavigateTo(url, true);
await ToastService.Info(SmartTranslateService.Translate("Starting download"));
}
}
else
await ToastService.Error(SmartTranslateService.Translate("You are not able to download folders using the moonlight file manager"));
}
});
actions.Add(new()
{
Id = "compress",
Name = "Compress",
Action = async (x) =>
{
await Access.Compress(x);
await View!.Refresh();
}
});
actions.Add(new()
{
Id = "decompress",
Name = "Decompress",
Action = async (x) =>
{
await Access.Decompress(x);
await View!.Refresh();
}
});
actions.Add(new()
{
Id = "move",
Name = "Move",
Action = async (x) =>
{
SingleMoveFile = x;
await StartMoveFiles();
}
});
actions.Add(new()
{
Id = "delete",
Name = "Delete",
Action = async (x) =>
{
await Access.Delete(x);
await View!.Refresh();
}
});
Actions = actions.ToArray();
}
private async Task<bool> OnElementClicked(FileData fileData)
{
if (fileData.IsFile)
{
EditorInitialData = await Access.Read(fileData);
EditorLanguage = MonacoTypeHelper.GetEditorType(fileData.Name);
EditingFile = fileData;
Editing = true;
await InvokeAsync(StateHasChanged);
return true;
}
await InvokeAsync(StateHasChanged);
return false;
}
private async void Save()
{
var data = await Editor.GetData();
await Access.Write(EditingFile, data);
await ToastService.Success(SmartTranslateService.Translate("Successfully saved file"));
}
private async void Cancel(bool save = false)
{
if (save)
{
var data = await Editor.GetData();
await Access.Write(EditingFile, data);
}
Editing = false;
await InvokeAsync(StateHasChanged);
}
private async Task Launch()
{
var url = await Access.GetLaunchUrl();
NavigationManager.NavigateTo(url, true);
}
private async Task CreateFolder()
{
var name = await AlertService.Text(
SmartTranslateService.Translate("Create a new folder"),
SmartTranslateService.Translate("Enter a name"),
""
);
if (string.IsNullOrEmpty(name))
return;
await Access.MkDir(name);
await View!.Refresh();
}
private async Task CreateFile()
{
var name = await AlertService.Text(
SmartTranslateService.Translate("Create a new file"),
SmartTranslateService.Translate("Enter a name"),
""
);
if (string.IsNullOrEmpty(name))
return;
await Access.Write(new FileData { IsFile = true, Name = name }, "");
await View!.Refresh();
}
private async Task OnSelectionChanged()
{
await InvokeAsync(StateHasChanged);
}
private async Task StartMoveFiles()
{
await FileSelectModal.Show();
}
private async Task DeleteMultiple()
{
foreach (var data in View!.SelectedFiles)
{
await Access.Delete(data);
}
await View!.Refresh();
}
private async Task CompressMultiple()
{
await Access.Compress(View!.SelectedFiles);
await View!.Refresh();
}
private async Task OnFileMoveSubmit(string path)
{
foreach (var sFile in View!.SelectedFiles)
{
await Access.Move(sFile, path + sFile.Name);
}
if (SingleMoveFile != null)
{
await Access.Move(SingleMoveFile, path + SingleMoveFile.Name);
SingleMoveFile = null;
}
await View.Refresh();
}
// This method can be called by every component to refresh the view
private async Task OnComponentStateChanged()
{
await View!.Refresh();
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,47 +0,0 @@
@using Moonlight.App.Helpers.Files
<div class="badge badge-lg badge-light-primary">
<div class="d-flex align-items-center flex-wrap">
@{
var vx = "/";
}
<a @onclick:preventDefault @onclick="() => SetPath(vx)" href="#">/</a>
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
</svg>
</span>
@{
var cp = "/";
var lp = "/";
var pathParts = Access.CurrentPath.Replace("\\", "/").Split('/', StringSplitOptions.RemoveEmptyEntries);
foreach (var path in pathParts)
{
lp = cp;
<a @onclick:preventDefault @onclick="() => SetPath(lp)" href="#">@(path)</a>
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
</svg>
</span>
cp += path + "/";
}
}
</div>
</div>
@code
{
[Parameter]
public FileAccess Access { get; set; }
[Parameter]
public Func<Task>? OnPathChanged { get; set; }
public async Task SetPath(string path)
{
await Access.SetDir(path);
OnPathChanged?.Invoke();
}
}

View File

@@ -1,132 +0,0 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject ModalService ModalService
@inject SmartTranslateService SmartTranslateService
<div class="modal" id="fileView@(Id)" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
@(Title)
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<FileView @ref="FileView"
Access="Access"
HideSelect="true"
Filter="DoFilter"
OnElementClicked="OnElementClicked">
</FileView>
</div>
<div class="modal-footer">
<WButton Text="@(SmartTranslateService.Translate("Submit"))"
WorkingText="@(SmartTranslateService.Translate("Processing"))"
CssClasses="btn-primary"
OnClick="Submit">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Cancel"))"
WorkingText="@(SmartTranslateService.Translate("Processing"))"
CssClasses="btn-danger"
OnClick="Cancel">
</WButton>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public FileAccess Access { get; set; }
[Parameter]
public bool OnlyFolder { get; set; } = false;
[Parameter]
public Func<FileData, bool>? Filter { get; set; }
[Parameter]
public string Title { get; set; } = "Select file or folder";
[Parameter]
public Func<string, Task>? OnSubmit { get; set; }
[Parameter]
public Func<Task>? OnCancel { get; set; }
private int Id = 0;
private string Result = "/";
private FileView FileView;
protected override void OnInitialized()
{
Id = this.GetHashCode();
}
public async Task Show()
{
// Reset
Result = "/";
await Access.SetDir("/");
await FileView.Refresh();
await ModalService.Show("fileView" + Id);
}
public async Task Hide()
{
await Cancel();
}
private async Task Cancel()
{
await ModalService.Hide("fileView" + Id);
await OnCancel?.Invoke()!;
}
private async Task Submit()
{
await ModalService.Hide("fileView" + Id);
await OnSubmit?.Invoke(Result)!;
}
private bool DoFilter(FileData file)
{
if (OnlyFolder)
{
if (file.IsFile)
return false;
else
{
if (Filter != null)
return Filter.Invoke(file);
else
return true;
}
}
else
{
if (Filter != null)
return Filter.Invoke(file);
else
return true;
}
}
private async Task<bool> OnElementClicked(FileData file)
{
Result = Access.CurrentPath + file.Name + (file.IsFile ? "" : "/");
if (!OnlyFolder && file.IsFile)
{
await Submit();
}
return false;
}
}

View File

@@ -1,90 +0,0 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Moonlight.App.Helpers
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
<label for="fileUpload" class="btn btn-primary me-3 @(Uploading ? "disabled" : "")">
@if (Uploading)
{
<span>@(Percent)%</span>
}
else
{
<span class="svg-icon svg-icon-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
</svg>
</span>
<TL>Upload</TL>
}
</label>
@code
{
[Parameter]
public FileAccess Access { get; set; }
[Parameter]
public Func<Task> OnUploadComplete { get; set; }
private bool Uploading = false;
private int Percent = 0;
private async Task OnFileChanged(InputFileChangeEventArgs arg)
{
await ToastService.CreateProcessToast("upload", SmartTranslateService.Translate("Uploading files"));
Uploading = true;
await InvokeAsync(StateHasChanged);
int i = 1;
foreach (var browserFile in arg.GetMultipleFiles())
{
if (browserFile.Size < 1024 * 1024 * 100)
{
Percent = 0;
try
{
await Access.Upload(
browserFile.Name,
browserFile.OpenReadStream(1024 * 1024 * 100),
async (i) =>
{
Percent = i;
Task.Run(() => { InvokeAsync(StateHasChanged); });
});
OnUploadComplete?.Invoke();
}
catch (Exception e)
{
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while uploading a file"));
Logger.Error("Error uploading file");
Logger.Error(e);
}
await ToastService.UpdateProcessToast("upload", $"{i}/{arg.GetMultipleFiles().Count} {SmartTranslateService.Translate("complete")}");
}
else
{
await ToastService.Error(SmartTranslateService.Translate("The uploaded file should not be bigger than 100MB"));
}
i++;
}
Uploading = false;
await InvokeAsync(StateHasChanged);
await ToastService.UpdateProcessToast("upload", SmartTranslateService.Translate("Upload complete"));
await ToastService.RemoveProcessToast("upload");
}
}

View File

@@ -1,287 +0,0 @@
@using Moonlight.App.Helpers.Files
@using BlazorContextMenu
@using Moonlight.App.Helpers
<div class="table-responsive single-color-scrollbar">
<div class="dataTables_scroll">
<div class="dataTables_scrollHead">
<div class="dataTables_scrollHeadInner">
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer">
<thead>
<tr class="text-start text-gray-400 fw-bold fs-7 text-uppercase gs-0">
<th class="w-10px pe-2 sorting_disabled">
@if (!HideSelect)
{
<div class="form-check form-check-sm form-check-custom form-check-solid me-3">
@if (AllToggled)
{
<input @onclick="() => SetToggleState(false)" class="form-check-input" type="checkbox" checked="">
}
else
{
<input @onclick="() => SetToggleState(true)" class="form-check-input" type="checkbox">
}
</div>
}
</th>
<th class="min-w-250px sorting_disabled">Name</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="dataTables_scrollBody" style="@(DisableScrolling ? "" : "position: relative; overflow: auto; max-height: 700px; width: 100%;")">
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer" style="width: 100%;">
<tbody class="fw-semibold text-gray-600">
<LazyLoader Load="Load">
<ContentBlock @ref="ContentBlock" AllowContentOverride="true">
@if (Access.CurrentPath != "/")
{
<tr class="even">
<td class="w-10px">
</td>
<td>
<div class="d-flex align-items-center">
<span class="icon-wrapper">
<i class="bx bx-md bx-up-arrow-alt text-primary"></i>
</span>
<a href="#" @onclick:preventDefault @onclick="GoUp" class="ms-3 text-gray-800 text-hover-primary">
<TL>Go up</TL>
</a>
</div>
</td>
<td></td>
<td class="text-end">
<div class="d-flex justify-content-end">
<div class="ms-2">
</div>
</div>
</td>
</tr>
}
@foreach (var file in Data)
{
<tr class="even">
<td class="w-10px">
@if (!HideSelect)
{
<div class="form-check form-check-sm form-check-custom form-check-solid">
@{
var toggle = ToggleStatusCache.ContainsKey(file) && ToggleStatusCache[file];
}
@if (toggle)
{
<input @onclick="() => SetToggleState(file, false)" class="form-check-input" type="checkbox" checked="checked">
}
else
{
<input @onclick="() => SetToggleState(file, true)" class="form-check-input" type="checkbox">
}
</div>
}
</td>
<td>
<div class="d-flex align-items-center">
<span class="icon-wrapper">
@if (file.IsFile)
{
<i class="bx bx-md bx-file text-primary"></i>
}
else
{
<i class="bx bx-md bx-folder text-primary"></i>
}
</span>
<a href="#" @onclick:preventDefault @onclick="() => OnClicked(file)" class="ms-3 text-gray-800 text-hover-primary">@(file.Name)</a>
</div>
</td>
<td>@(Formatter.FormatSize(file.Size))</td>
<td class="text-end">
<div class="d-flex justify-content-end">
<div class="ms-2 me-6">
@if (ContextActions.Any())
{
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="file">
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
<span class="svg-icon svg-icon-5 m-0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
</svg>
</span>
</button>
</ContextMenuTrigger>
}
</div>
</div>
</td>
</tr>
}
</ContentBlock>
</LazyLoader>
</tbody>
</table>
</div>
</div>
</div>
@if (ContextActions.Any())
{
<ContextMenu Id="triggerMenu" CssClass="bg-secondary z-10">
@foreach (var action in ContextActions)
{
<Item Id="@action.Id" OnClick="OnContextMenuClick">
<TL>@action.Name</TL>
</Item>
}
</ContextMenu>
}
@code
{
[Parameter]
public FileAccess Access { get; set; }
[Parameter]
public Func<FileData, Task<bool>>? OnElementClicked { get; set; }
[Parameter]
public Func<Task>? OnSelectionChanged { get; set; }
[Parameter]
public ContextAction[] ContextActions { get; set; } = Array.Empty<ContextAction>();
[Parameter]
public bool HideSelect { get; set; } = false;
[Parameter]
public bool DisableScrolling { get; set; } = false;
[Parameter]
public Func<FileData, bool>? Filter { get; set; }
public FileData[] SelectedFiles => ToggleStatusCache
.Where(x => x.Value)
.Select(x => x.Key)
.ToArray();
private FileData[] Data = Array.Empty<FileData>();
private Dictionary<FileData, bool> ToggleStatusCache = new();
private bool AllToggled = false;
private ContentBlock ContentBlock;
public async Task Refresh()
{
ContentBlock?.SetBlocking(true);
var list = new List<FileData>();
foreach (var fileData in await Access.Ls())
{
if (Filter != null)
{
if (Filter.Invoke(fileData))
list.Add(fileData);
}
else
list.Add(fileData);
}
Data = list.ToArray();
ToggleStatusCache.Clear();
AllToggled = false;
foreach (var fileData in Data)
{
ToggleStatusCache.Add(fileData, false);
}
await InvokeAsync(StateHasChanged);
OnSelectionChanged?.Invoke();
ContentBlock?.SetBlocking(false);
}
private async Task Load(LazyLoader arg)
{
await Refresh();
}
private async Task SetToggleState(FileData fileData, bool status)
{
if (ToggleStatusCache.ContainsKey(fileData))
ToggleStatusCache[fileData] = status;
else
ToggleStatusCache.Add(fileData, status);
await InvokeAsync(StateHasChanged);
OnSelectionChanged?.Invoke();
}
private async Task SetToggleState(bool status)
{
AllToggled = status;
foreach (var fd in ToggleStatusCache.Keys)
{
ToggleStatusCache[fd] = status;
}
await InvokeAsync(StateHasChanged);
OnSelectionChanged?.Invoke();
}
private async Task OnClicked(FileData fileData)
{
if (OnElementClicked != null)
{
var canceled = await OnElementClicked.Invoke(fileData);
if (canceled)
return;
}
if (!fileData.IsFile)
{
await Access.Cd(fileData.Name);
await Refresh();
}
}
private async Task GoUp()
{
if (OnElementClicked != null)
{
var canceled = await OnElementClicked.Invoke(new()
{
Name = "..",
IsFile = false
});
if (canceled)
return;
}
await Access.Up();
await Refresh();
}
private Task OnContextMenuClick(ItemClickEventArgs eventArgs)
{
var action = ContextActions.FirstOrDefault(x => x.Id == eventArgs.MenuItem.Id);
if (action != null)
{
action.Action.Invoke((FileData)eventArgs.Data);
}
return Task.CompletedTask;
}
}

View File

@@ -1,55 +0,0 @@
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject SmartTranslateService SmartTranslateService
@inject AlertService AlertService
@if (!Working)
{
<button class="btn btn-danger p-4 @AdditionalCssClasses" @onclick="Do">
<i class="bx bx-trash p-0 px-2"></i>
</button>
}
else
{
<button class="btn btn-danger p-4 disabled @AdditionalCssClasses" disabled="">
<span class="spinner-border spinner-border-sm align-middle me-2 p-0 px-2"></span>
</button>
}
@code
{
private bool Working { get; set; } = false;
[Parameter]
public Func<Task>? OnClick { get; set; }
[Parameter]
public bool Confirm { get; set; } = false;
[Parameter]
public string AdditionalCssClasses { get; set; } = "";
private async Task Do()
{
Working = true;
StateHasChanged();
await Task.Run(async () =>
{
if (Confirm)
{
var b = await AlertService.ConfirmMath();
if (b)
{
if(OnClick != null)
await OnClick.Invoke();
}
}
Working = false;
await InvokeAsync(StateHasChanged);
});
}
}

View File

@@ -1,92 +0,0 @@
@typeparam T
@inherits InputBase<T>
<div class="dropdown w-100">
<div class="input-group">
@if (CurrentValue == null)
{
<input class="form-control" type="text" @bind-value="SearchTerm" @bind-value:event="oninput" placeholder="Search...">
}
else
{
<input class="form-control" type="text" value="@(DisplayFunc(CurrentValue))">
<button class="btn btn-primary" @onclick="() => SelectItem(default(T)!)">
<i class="bx bx-md bx-x"></i>
</button>
}
</div>
@{
var anyItems = FilteredItems.Any();
}
<div class="dropdown-menu w-100 @(anyItems ? "show" : "")" style="max-height: 200px; overflow-y: auto;">
@if (anyItems)
{
foreach (var item in FilteredItems)
{
<button class="dropdown-item py-2" type="button" @onclick="(() => SelectItem(item))">@DisplayFunc(item)</button>
}
}
</div>
</div>
@code {
[Parameter]
public IEnumerable<T> Items { get; set; }
[Parameter]
public Func<T, string> DisplayFunc { get; set; }
[Parameter]
public Func<T, string> SearchProp { get; set; }
private string SearchTerm
{
get => searchTerm;
set
{
FilteredItems = Items.Where(i => SearchProp(i).Contains(SearchTerm, StringComparison.OrdinalIgnoreCase)).ToList();
searchTerm = value;
}
}
private string searchTerm = "";
private List<T> FilteredItems = new();
private void SelectItem(T item)
{
CurrentValue = item;
SearchTerm = "";
FilteredItems.Clear();
}
protected override bool TryParseValueFromString(string? value, out T result, out string? validationErrorMessage)
{
// Check if the value is null or empty
if (string.IsNullOrEmpty(value))
{
result = default(T)!;
validationErrorMessage = "Value cannot be null or empty";
return false;
}
// Try to find an item that matches the search term
var item = FilteredItems.FirstOrDefault(i => SearchProp(i).Equals(value, StringComparison.OrdinalIgnoreCase));
if (item != null)
{
result = item;
validationErrorMessage = null;
return true;
}
else
{
result = default(T)!;
validationErrorMessage = $"No item found for search term '{value}'";
return false;
}
}
}

View File

@@ -1,60 +0,0 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden=""/>
<label for="fileUpload" class="btn btn-primary me-3">
@if (SelectedFile != null)
{
<div class="input-group">
<input type="text" class="form-control" value="@(SelectedFile.Name)">
<button class="btn btn-danger" type="button" @onclick="RemoveSelection">
<i class="bx bx-md bx-x"></i>
</button>
</div>
}
else
{
<span class="svg-icon svg-icon-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
</svg>
</span>
}
</label>
@code
{
public IBrowserFile? SelectedFile { get; set; }
private async Task OnFileChanged(InputFileChangeEventArgs arg)
{
if (arg.FileCount > 0)
{
if (arg.File.Size < 1024 * 1024 * 5)
{
SelectedFile = arg.File;
await InvokeAsync(StateHasChanged);
return;
}
await ToastService.Error(SmartTranslateService.Translate("The uploaded file should not be bigger than 5MB"));
}
SelectedFile = null;
await InvokeAsync(StateHasChanged);
}
public async Task RemoveSelection()
{
SelectedFile = null;
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,105 +0,0 @@
@using Moonlight.App.Helpers
<div class="form @CssClass">
<EditForm @ref="EditForm" Model="Model" OnValidSubmit="ValidSubmit" OnInvalidSubmit="InvalidSubmit">
<DataAnnotationsValidator></DataAnnotationsValidator>
@if (Working)
{
<div class="d-flex flex-center flex-column">
<span class="fs-1 spinner-border spinner-border-lg align-middle me-2"></span>
<span class="mt-3 fs-5"><TL>Proccessing</TL></span>
</div>
}
else
{
if (ErrorMessages.Any())
{
<div class="alert alert-danger p-10 mb-3">
@foreach (var msg in ErrorMessages)
{
<TL>@(msg)</TL>
<br/>
}
</div>
}
@(ChildContent)
}
</EditForm>
</div>
@code
{
[Parameter]
public object Model { get; set; }
[Parameter]
public Func<Task>? OnValidSubmit { get; set; }
[Parameter]
public Func<Task>? OnInvalidSubmit { get; set; }
[Parameter]
public Func<Task>? OnSubmit { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public string CssClass { get; set; }
private EditForm EditForm;
private List<string> ErrorMessages = new();
private bool Working = false;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
EditForm.EditContext!.SetFieldCssClassProvider(new FieldCssHelper());
}
}
private async Task ValidSubmit(EditContext context)
{
ErrorMessages.Clear();
Working = true;
await InvokeAsync(StateHasChanged);
await Task.Run(async () =>
{
await InvokeAsync(async () =>
{
if (OnValidSubmit != null)
await OnValidSubmit.Invoke();
if (OnSubmit != null)
await OnSubmit.Invoke();
});
Working = false;
await InvokeAsync(StateHasChanged);
});
}
private async Task InvalidSubmit(EditContext context)
{
ErrorMessages.Clear();
context.Validate();
foreach (var message in context.GetValidationMessages())
{
ErrorMessages.Add(message);
}
await InvokeAsync(StateHasChanged);
if (OnInvalidSubmit != null)
await OnInvalidSubmit.Invoke();
if (OnSubmit != null)
await OnSubmit.Invoke();
}
}

View File

@@ -1,65 +0,0 @@
@using System.Reflection
@using System.Collections
@using Moonlight.App.Helpers
<div class="accordion my-3" id="configSetting@(Model.GetHashCode())">
<div class="accordion-item">
<h2 class="accordion-header" id="configSetting-header@(Model.GetHashCode())">
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#configSetting-body@(Model.GetHashCode())" aria-expanded="false" aria-controls="configSetting-body@(Model.GetHashCode())">
@{
var name = Formatter.ReplaceEnd(Model.GetType().Name, "Data", "");
name = Formatter.ConvertCamelCaseToSpaces(name);
}
@(name)
</button>
</h2>
<div id="configSetting-body@(Model.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="configSetting-header@(Model.GetHashCode())" data-bs-parent="#configSetting">
<div class="accordion-body">
@foreach (var property in Model.GetType().GetProperties())
{
@BindAndRenderProperty(property)
}
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public object Model { get; set; }
private RenderFragment BindAndRenderProperty(PropertyInfo property)
{
if (property.PropertyType.IsClass && !property.PropertyType.IsPrimitive && !typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
{
return @<SmartFormClass Model="@property.GetValue(Model)"/>;
// If the property is a subclass, serialize and generate form for it
/*
foreach (var subProperty in property.PropertyType.GetProperties())
{
return BindAndRenderProperty(subProperty);
}*/
}
else if (property.PropertyType == typeof(int) || property.PropertyType == typeof(string) || property.PropertyType == typeof(bool) || property.PropertyType == typeof(decimal) || property.PropertyType == typeof(long))
{
return @<SmartFormProperty Model="Model" PropertyInfo="property"/>;
}
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
{
// If the property is a collection, generate form for each element
var collection = property.GetValue(Model) as IEnumerable;
if (collection != null)
{
foreach (var element in collection)
{
}
}
}
// Additional property types could be handled here (e.g., DateTime, int, etc.)
return @<div></div>;
}
}

View File

@@ -1,79 +0,0 @@
@using System.Reflection
@using Moonlight.App.Helpers
@using System.ComponentModel
<label class="form-label" for="@PropertyInfo.Name">
@(Formatter.ConvertCamelCaseToSpaces(PropertyInfo.Name))
</label>
@{
//TODO: Tidy up this code
var attrs = PropertyInfo.GetCustomAttributes(true);
var descAttr = attrs
.FirstOrDefault(x => x.GetType() == typeof(DescriptionAttribute));
var blurBool = attrs.Any(x => x.GetType() == typeof(BlurAttribute));
var blur = blurBool ? "blur-unless-hover" : "";
}
@if (descAttr != null)
{
var a = descAttr as DescriptionAttribute;
<div class="form-text fs-5 mb-2 mt-0">
@(a.Description)
</div>
}
<div class="input-group mb-5">
@if (PropertyInfo.PropertyType == typeof(string))
{
var binder = new PropBinder(PropertyInfo, Model!);
<div class="@(blur) w-100">
<InputText id="@PropertyInfo.Name" @bind-Value="binder.StringValue" class="form-control"/>
</div>
}
else if (PropertyInfo.PropertyType == typeof(int))
{
var binder = new PropBinder(PropertyInfo, Model!);
<InputNumber id="@PropertyInfo.Name" @bind-Value="binder.IntValue" class="form-control"/>
}
else if (PropertyInfo.PropertyType == typeof(long))
{
var binder = new PropBinder(PropertyInfo, Model!);
<InputNumber id="@PropertyInfo.Name" @bind-Value="binder.LongValue" class="form-control"/>
}
else if (PropertyInfo.PropertyType == typeof(bool))
{
var binder = new PropBinder(PropertyInfo, Model!);
<div class="form-check">
<InputCheckbox id="@PropertyInfo.Name" @bind-Value="binder.BoolValue" class="form-check-input"/>
</div>
}
else if (PropertyInfo.PropertyType == typeof(DateTime))
{
var binder = new PropBinder(PropertyInfo, Model!);
<InputDate id="@PropertyInfo.Name" @bind-Value="binder.DateTimeValue" class="form-control"/>
}
else if (PropertyInfo.PropertyType == typeof(decimal))
{
var binder = new PropBinder(PropertyInfo, Model!);
<InputNumber id="@PropertyInfo.Name" step="0.01" @bind-Value="binder.DoubleValue" class="form-control"/>
}
</div>
@code
{
[Parameter]
public PropertyInfo PropertyInfo { get; set; }
[Parameter]
public object Model { get; set; }
}

View File

@@ -1,48 +0,0 @@
@using Moonlight.App.Services.Interop
@inject ReCaptchaService ReCaptchaService
@inherits InputBase<bool>
<div class="d-flex flex-center" id="@UniqueId"></div>
@code
{
private string UniqueId = Guid.NewGuid().ToString();
private string Id;
private async Task OnValid()
{
CurrentValue = true;
await ValueChanged.InvokeAsync(CurrentValue);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
ReCaptchaService.OnValidResponse += OnValid;
if(await ReCaptchaService.IsEnabled())
Id = await ReCaptchaService.Create(UniqueId);
else
{
await OnValid();
}
}
}
protected override bool TryParseValueFromString(string? value, out bool result, out string? validationErrorMessage)
{
if (bool.TryParse(value, out result))
{
validationErrorMessage = null;
return true;
}
else
{
validationErrorMessage = "Invalid value.";
return false;
}
}
}

View File

@@ -1,63 +0,0 @@
@typeparam TField
@inherits InputBase<TField>
<select class="form-select" @bind="Binding">
@foreach(var item in Items)
{
<option value="@(item!.GetHashCode())">@(DisplayField(item))</option>
}
</select>
@code
{
[Parameter]
public IEnumerable<TField> Items { get; set; }
[Parameter]
public Func<TField, string> DisplayField { get; set; }
[Parameter]
public Func<Task>? OnChange { get; set; }
protected override void OnInitialized()
{
}
protected override string? FormatValueAsString(TField? value)
{
if (value == null)
return null;
return DisplayField.Invoke(value);
}
protected override bool TryParseValueFromString(string? value, out TField result, out string? validationErrorMessage)
{
validationErrorMessage = "";
result = default(TField)!;
return false;
}
private int Binding
{
get
{
if (Value == null)
return -1;
return Value.GetHashCode();
}
set
{
var i = Items.FirstOrDefault(x => x!.GetHashCode() == value);
if (i != null)
{
Value = i;
ValueChanged.InvokeAsync(i);
OnChange?.Invoke();
}
}
}
}

View File

@@ -1,48 +0,0 @@
@if (!Working)
{
<button class="btn @(CssClasses)" @onclick="Do">
@Text
@ChildContent
</button>
}
else
{
<button class="btn @(CssClasses) disabled" disabled="">
<span class="spinner-border spinner-border-sm align-middle me-2"></span>
@WorkingText
</button>
}
@code
{
private bool Working { get; set; } = false;
[Parameter]
public string CssClasses { get; set; } = "btn-primary";
[Parameter]
public string Text { get; set; } = "";
[Parameter]
public string WorkingText { get; set; } = "";
[Parameter]
public Func<Task>? OnClick { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
private async Task Do()
{
Working = true;
StateHasChanged();
await Task.Run(async () =>
{
if(OnClick != null)
await OnClick.Invoke();
Working = false;
await InvokeAsync(StateHasChanged);
});
}
}

View File

@@ -1,22 +0,0 @@
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/domains">
<TL>Domains</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/domains/shared">
<TL>Shared domains</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -1,42 +0,0 @@
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/security">
<TL>Overview</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/security/malware">
<TL>Malware</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/security/ipbans">
<TL>Ip bans</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 3 ? "active" : "")" href="/admin/security/permissiongroups">
<TL>Permission groups</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 4 ? "active" : "")" href="/admin/security/logs">
<TL>Logs</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 5 ? "active" : "")" href="/admin/security/ddos">
<TL>Ddos protection</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -1,37 +0,0 @@
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/servers">
<TL>Overview</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/servers/manager">
<TL>Manager</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/servers/cleanup">
<TL>Cleanup</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 3 ? "active" : "")" href="/admin/nodes">
<TL>Nodes</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 4 ? "active" : "")" href="/admin/servers/images">
<TL>Images</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -1,51 +0,0 @@
@using Moonlight.App.Database.Entities
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/servers/view/@(Server.Id)">
<TL>Overview</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/servers/view/@(Server.Id)/image">
<TL>Image</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/servers/view/@(Server.Id)/resources">
<TL>Resources</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 3 ? "active" : "")" href="/admin/servers/view/@(Server.Id)/allocations">
<TL>Allocations</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 4 ? "active" : "")" href="/admin/servers/view/@(Server.Id)/archive">
<TL>Archive</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 5 ? "active" : "")" href="/admin/servers/view/@(Server.Id)/debug">
<TL>Debug</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 6 ? "active" : "")" href="/admin/servers/view/@(Server.Id)/delete">
<TL>Delete</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; }
[Parameter]
public Server Server { get; set; }
}

View File

@@ -1,22 +0,0 @@
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/users">
<TL>Users</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/users/sessions">
<TL>Sessions</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -1,22 +0,0 @@
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/statistics">
<TL>Overview</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/statistics/live">
<TL>Live data</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -1,50 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/system">
<TL>Overview</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 5 ? "active" : "")" href="/admin/system/resources">
<TL>Resources</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 6 ? "active" : "")" href="/admin/system/discordbot">
<TL>Discord bot</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 7 ? "active" : "")" href="/admin/system/news">
<TL>News</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 8 ? "active" : "")" href="/admin/system/configuration">
<TL>Configuration</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 9 ? "active" : "")" href="/admin/system/mail">
<TL>Mail</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 10 ? "active" : "")" href="/admin/system/plugins">
<TL>Plugins</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -1,22 +0,0 @@
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/webspaces">
<TL>Webspaces</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/webspaces/servers">
<TL>Cloud panels</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -1,59 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services.Sessions
@inject IdentityService IdentityService
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-9 pb-0">
<div class="d-flex flex-wrap flex-sm-nowrap mb-3">
<div class="flex-grow-1">
<div class="d-flex justify-content-between align-items-start flex-wrap mb-2">
<div class="d-flex flex-column">
<div class="d-flex align-items-center mb-2">
<a class="text-gray-900 fs-2 fw-bold me-1 @(IdentityService.User.StreamerMode ? "blur" : "")">@(IdentityService.User.FirstName) @(IdentityService.User.LastName)</a>
@if (IdentityService.User.Status == UserStatus.Verified)
{
<i class="text-success bx bx-md bxs-badge-check"></i>
}
</div>
<div class="d-flex flex-wrap fw-semibold fs-6 mb-4 pe-2">
<span class="d-flex align-items-center text-gray-400 mb-2 @(IdentityService.User.StreamerMode ? "blur" : "")">
@(IdentityService.User.Email)
</span>
</div>
</div>
</div>
</div>
</div>
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/profile">
<TL>Overview</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/profile/discord">
<TL>Discord</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/profile/security">
<TL>Security</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 3 ? "active" : "")" href="/profile/subscriptions">
<TL>Subscriptions</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -1,52 +0,0 @@
@if (AllowContentOverride)
{
if (IsBlocking)
{
<div class="overlay overlay-block">
<div class="overlay-wrapper p-5">
@(ChildContent)
</div>
<div class="overlay-layer">
<div class="spinner-border text-primary" role="status">
</div>
</div>
</div>
}
else
{
@ChildContent
}
}
else
{
<div class="@(IsBlocking ? "overlay overlay-block" : "")">
<div class="@(IsBlocking ? "overlay-wrapper p-5" : "")">
@(ChildContent)
</div>
@if (IsBlocking)
{
<div class="overlay-layer">
<div class="spinner-border text-primary" role="status">
</div>
</div>
}
</div>
}
@code
{
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public bool AllowContentOverride { get; set; } = false;
private bool IsBlocking = false;
public async Task SetBlocking(bool b)
{
IsBlocking = b;
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,37 +0,0 @@
@using Moonlight.App.Services
@inject ConfigService ConfigService
@{
var marketingConfig = ConfigService
.Get()
.Moonlight.Marketing;
}
<div id="kt_app_footer" class="app-footer">
<div class="app-container container-fluid d-flex flex-column flex-md-row flex-center flex-md-stack py-3">
<div class="text-dark order-2 order-md-1">
<span class="text-muted fw-semibold me-1">2022 - @DateTime.Now.Year©</span>
<a href="@(marketingConfig.Website)" target="_blank" class="text-gray-800 text-hover-primary">
@(marketingConfig.BrandName)
</a>
</div>
<ul class="menu menu-gray-600 menu-hover-primary fw-semibold order-1">
<li class="menu-item">
<a href="@(marketingConfig.About)" target="_blank" class="menu-link px-2">
<TL>About us</TL>
</a>
</li>
<li class="menu-item">
<a href="@(marketingConfig.Imprint)" target="_blank" class="menu-link px-2">
<TL>Imprint</TL>
</a>
</li>
<li class="menu-item">
<a href="@(marketingConfig.Privacy)" target="_blank" class="menu-link px-2">
<TL>Privacy</TL>
</a>
</li>
</ul>
</div>
</div>

View File

@@ -1,59 +0,0 @@
@using Moonlight.App.Models.Misc
@using System.Text
@using Moonlight.App.Helpers
@{
string GetStatusColor(string s)
{
if (s == "Healthy")
return "success";
else if (s == "Unhealthy")
return "danger";
else
return "warning";
}
}
<div class="card">
<div class="card-header">
<div class="card-title">
<TL>Moonlight health</TL>:
<div class="ps-3 text-@(GetStatusColor(HealthCheck.Status))">
<TL>@(HealthCheck.Status)</TL>
</div>
</div>
</div>
<div class="card-body">
<div class="accordion" id="healthCheck">
@foreach (var entry in HealthCheck.Entries)
{
<div class="accordion-item">
<h2 class="accordion-header" id="healthCheck_1_header_@(entry.Key.ToLower())">
<button class="accordion-button fs-4 fw-semibold text-@(GetStatusColor(entry.Value.Status))" type="button" data-bs-toggle="collapse" data-bs-target="#healthCheck_body_@(entry.Key.ToLower())">
@(entry.Key)
</button>
</h2>
<div id="healthCheck_body_@(entry.Key.ToLower())" class="accordion-collapse collapse" data-bs-parent="#healthCheck">
<div class="accordion-body">
<b><TL>Status</TL>:</b>&nbsp;<TL>@(entry.Value.Status)</TL><br/>
<b><TL>Description</TL>:</b>&nbsp;@(entry.Value.Description)<br/>
<br/>
@foreach (var x in entry.Value.Data)
{
<b>@(x.Key)</b>
<br/>
@(x.Value)<br/>
}
</div>
</div>
</div>
}
</div>
</div>
</div>
@code
{
[Parameter]
public HealthCheck HealthCheck { get; set; }
}

View File

@@ -1,50 +0,0 @@
@if (loaded)
{
@ChildContent
}
else
{
<div class="d-flex flex-center flex-column">
<span class="fs-1 spinner-border spinner-border-lg align-middle me-2"></span>
<span class="mt-3 fs-5">@(Text)</span>
</div>
}
@code
{
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public Func<LazyLoader, Task> Load { get; set; }
[Parameter]
public string Text { get; set; } = "";
private bool loaded = false;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await Load.Invoke(this);
loaded = true;
await InvokeAsync(StateHasChanged);
}
}
public async Task SetText(string text)
{
Text = text;
await InvokeAsync(StateHasChanged);
}
public async Task Reload()
{
loaded = false;
await InvokeAsync(StateHasChanged);
await Load.Invoke(this);
loaded = true;
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,104 +0,0 @@
@using Moonlight.App.Helpers
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@using Moonlight.App.Services.Sessions
@using Moonlight.App.Database.Entities
@using Task = System.Threading.Tasks.Task
@inject IdentityService IdentityService
@inject NavigationManager NavigationManager
@inject CookieService CookieService
@if (User != null)
{
<div class="menu menu-column justify-content-center"
data-kt-menu="true">
<div class="menu-item">
<div class="dropdown">
<button class="btn btn-success dropdown-toggle" type="button" data-bs-toggle="dropdown">
<TL>Create</TL>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item py-2" href="/servers/create">
<TL>Server</TL>
</a>
</li>
<li>
<a class="dropdown-item py-2" href="/domains/create">
<TL>Domain</TL>
</a>
</li>
<li>
<a class="dropdown-item py-2" href="/webspaces/create">
<TL>Webspace</TL>
</a>
</li>
</ul>
</div>
</div>
</div>
}
<div class="app-navbar flex-shrink-0">
@if (User != null)
{
<div class="app-navbar-item ms-1 ms-lg-3">
<a href="/support" class="btn btn-icon btn-custom btn-icon-muted btn-active-light btn-active-color-primary w-35px h-35px w-md-40px h-md-40px position-relative">
<i class="bx bx-support"></i>
</a>
</div>
<div class="app-navbar-item ms-1 ms-lg-3 dropdown">
<!-- Trigger -->
<a class="cursor-pointer d-block symbol symbol-35px symbol-md-40px" href="#" role="button" id="dropdownMenuLink" data-bs-toggle="dropdown" aria-expanded="false">
<img alt="Avatar" src="/api/moonlight/avatar/@(User.Id)" width="35" height="35">
</a>
<!-- Dropdown Menu -->
<div class="dropdown-menu dropdown-menu-end w-275px p-5" aria-labelledby="dropdownMenuLink">
<div class="dropdown-item py-4 bg-body bg-hover-body ps-0">
<div class="d-flex align-items-center">
<img alt="Avatar" src="/api/moonlight/avatar/@(User.Id)" class="rounded-circle me-3" width="50" height="50">
<div class="d-flex flex-column">
<div class="fw-bold d-flex align-items-center">
<div class="@(User.StreamerMode ? "blur" : "")">
@(User.FirstName) @(User.LastName)
</div>
@if (User.Admin)
{
<span class="badge bg-success fw-bold ms-2">Admin</span>
}
</div>
<a class="fw-semibold text-muted text-decoration-none @(User.StreamerMode ? "blur" : "")">@User.Email</a>
</div>
</div>
</div>
<hr class="dropdown-divider">
<a href="/profile" class="dropdown-item rounded fs-7 py-4">Profile</a>
<a href="#" @onclick:preventDefault @onclick="Logout" class="dropdown-item rounded fs-7 py-4">Logout</a>
</div>
</div>
}
</div>
@code
{
private User? User;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
User = IdentityService.User;
await InvokeAsync(StateHasChanged);
}
}
private async void Logout()
{
await CookieService.SetValue("token", "", 1);
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}

View File

@@ -1,86 +0,0 @@
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services
@using Moonlight.App.Perms
@inject ModalService ModalService
@inject SmartTranslateService SmartTranslateService
<div id="permissionEditor" class="modal" tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<TL>Edit permissions</TL>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
@if (Enabled)
{
<div class="table-responsive">
<table class="table align-middle table-row-dashed fs-6 gy-5">
<tbody class="text-gray-600 fw-semibold">
@foreach (var permission in Permissions.GetAllPermissions())
{
<tr>
<td class="text-gray-800">
@(permission.Name)
</td>
<td>
@(permission.Description)
</td>
<td>
<div class="form-check form-switch form-check-custom form-check-solid">
<input class="form-check-input" type="checkbox" @bind="Storage[permission]"/>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<TL>Close</TL>
</button>
<WButton Text="@(SmartTranslateService.Translate("Save"))"
WorkingText="@(SmartTranslateService.Translate("Saving"))"
CssClasses="btn-primary"
OnClick="Save">
</WButton>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public byte[] InitialData { get; set; } = Array.Empty<byte>();
[Parameter]
public Func<byte[], Task>? OnSave { get; set; }
private bool Enabled = false;
private PermissionStorage Storage;
public async Task Launch()
{
Enabled = true;
Storage = new(InitialData);
await InvokeAsync(StateHasChanged);
await ModalService.Show("permissionEditor");
}
private async Task Save()
{
OnSave?.Invoke(Storage.Data);
await ModalService.Hide("permissionEditor");
Enabled = false;
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,132 +0,0 @@
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject RatingService RatingService
@inject ModalService ModalService
@inject SmartTranslateService SmartTranslateService
@inject ToastService ToastService
<div class="modal fade" tabindex="-1" style="display: none;" id="rating">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">
<TL>Hey, can i borrow you for a second?</TL>
</h3>
</div>
<div class="modal-body">
<p class="fs-4">
<TL>We want to improve our services and get a little bit of feedback how we are currently doing. Please leave us a rating</TL>
</p>
</div>
<div class="modal-body">
<div class="d-flex justify-content-center rating">
<input class="rating-input" name="rating" value="0" checked="" type="radio">
<label class="rating-label" for="rating1">
<i class="bx bx-lg bx-star"></i>
</label>
<input class="rating-input" name="rating" value="1" type="radio" id="rating1" @onchange="SetRating">
<label class="rating-label" for="rating2">
<i class="bx bx-lg bx-star"></i>
</label>
<input class="rating-input" name="rating" value="2" type="radio" id="rating2" @onchange="SetRating">
<label class="rating-label" for="rating3">
<i class="bx bx-lg bx-star"></i>
</label>
<input class="rating-input" name="rating" value="3" type="radio" id="rating3" @onchange="SetRating">
<label class="rating-label" for="rating4">
<i class="bx bx-lg bx-star"></i>
</label>
<input class="rating-input" name="rating" value="4" type="radio" id="rating4" @onchange="SetRating">
<label class="rating-label" for="rating5">
<i class="bx bx-lg bx-star"></i>
</label>
<input class="rating-input" name="rating" value="5" type="radio" id="rating5" @onchange="SetRating">
</div>
</div>
<div class="modal-footer">
<WButton Text="@(SmartTranslateService.Translate("Rate"))"
WorkingText="@(SmartTranslateService.Translate("Saving"))"
CssClasses="btn-primary"
OnClick="Save">
</WButton>
</div>
</div>
</div>
</div>
<div class="modal fade" tabindex="-1" style="display: none" id="ratingUrl">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">
<TL>Thanks for your rating</TL>
</h3>
</div>
<div class="modal-body">
<p class="fs-4">
<TL>It would be really kind of you rating us on a external platform as it will help our project very much</TL>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><TL>Close</TL></button>
<a href="@(Url)" class="btn btn-primary" target="_blank"><TL>Yes</TL></a>
</div>
</div>
</div>
</div>
@code
{
private int Rate = 0;
private string Url = "";
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Url = await RatingService.GetRateUrl();
if (await RatingService.ShouldRate())
{
await Task.Run(async () =>
{
await Task.Delay(1000);
await ModalService.Show("rating");
});
}
}
}
private Task SetRating(ChangeEventArgs args)
{
if (args.Value == null)
return Task.CompletedTask;
if (int.TryParse(args.Value.ToString(), out int rate))
{
Rate = rate;
}
return Task.CompletedTask;
}
private async Task Save()
{
await ModalService.Hide("rating");
if (await RatingService.Rate(Rate))
{
await ModalService.Show("ratingUrl");
}
else
{
await ToastService.Success(
SmartTranslateService.Translate("Rating saved")
);
}
}
}

View File

@@ -1,62 +0,0 @@
@using Moonlight.App.Services.Sessions
@using Moonlight.App.Perms
@using Moonlight.App.Helpers
@using Moonlight.Shared.Components.Alerts
@inject IdentityService IdentityService
@inject NavigationManager NavigationManager
@if (Allowed)
{
@ChildContent
}
else
{
<NoPermissionAlert />
}
@code
{
[CascadingParameter(Name = "TargetPageType")]
public Type TargetPageType { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
private bool Allowed = false;
protected override Task OnParametersSetAsync()
{
var attributes = TargetPageType.GetCustomAttributes(true);
var permAttrs = attributes
.Where(x => x.GetType() == typeof(PermissionRequired))
.Select(x => x as PermissionRequired)
.ToArray();
Allowed = true;
foreach (var permissionRequired in permAttrs)
{
var permission = Permissions.FromString(permissionRequired!.Name);
if (permission == null)
{
Allowed = false;
break;
}
if (!IdentityService.Permissions[permission])
{
Allowed = false;
break;
}
}
if (!Allowed)
{
Logger.Warn($"{IdentityService.Ip} has tried to access {NavigationManager.Uri} without permission", "security");
}
return Task.CompletedTask;
}
}

View File

@@ -1,30 +0,0 @@
@using Microsoft.AspNetCore.Components.Rendering
@using Moonlight.App.Services
@inject SmartTranslateService SmartTranslateService
@{
var x = "";
if (ChildContent != null)
{
var rb = new RenderTreeBuilder();
ChildContent.Invoke(rb);
foreach (var frame in rb.GetFrames().Array)
{
if (frame.Sequence != 0)
x += frame.MarkupContent;
}
x = SmartTranslateService.Translate(x);
}
}
<span>@(x)</span>
@code
{
[Parameter]
public RenderFragment? ChildContent { get; set; }
}

View File

@@ -1,20 +0,0 @@
@{
var route = "/" + (Router.Route ?? "");
}
@if (route == Path)
{
@ChildContent
}
@code
{
[CascadingParameter]
public SmartRouter Router { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public string Path { get; set; }
}

View File

@@ -1,12 +0,0 @@
<CascadingValue TValue="SmartRouter" Value="@this">
@ChildContent
</CascadingValue>
@code
{
[Parameter]
public string? Route { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
}

View File

@@ -1,236 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.App.Services
@using Moonlight.App.Services.Files
@using System.Text.RegularExpressions
@inject ResourceService ResourceService
@inject SmartTranslateService SmartTranslateService
<div class="scroll-y me-n5 pe-5" style="max-height: 50vh; display: flex; flex-direction: column-reverse;">
@foreach (var message in Messages.OrderByDescending(x => x.Id)) // Reverse messages to use auto scrolling
{
if (message.IsSupportMessage)
{
if (ViewAsSupport)
{
<div class="d-flex justify-content-end mb-10 ">
<div class="d-flex flex-column align-items-end">
<div class="d-flex align-items-center mb-2">
<div class="me-3">
<span class="text-muted fs-7 mb-1">@(Formatter.FormatAgoFromDateTime(message.CreatedAt, SmartTranslateService))</span>
@if (message.Sender != null)
{
<span class="fs-5 fw-bold text-gray-900 text-hover-primary ms-1">@(message.Sender!.FirstName) @(message.Sender!.LastName)</span>
}
</div>
<div class="symbol symbol-35px symbol-circle ">
@if (message.Sender != null)
{
<img alt="Avatar" src="@(ResourceService.Avatar(message.Sender))">
}
</div>
</div>
<div class="p-5 rounded bg-light-primary text-dark fw-semibold mw-lg-400px text-end">
@{
int i = 0;
var arr = message.Content.Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
}
@foreach (var line in arr)
{
@line
if (i++ != arr.Length - 1)
{
<br/>
}
}
@if (!string.IsNullOrEmpty(message.AttachmentUrl))
{
<div class="mt-3">
@if (Regex.IsMatch(message.AttachmentUrl, @"\.(jpg|jpeg|png|gif|bmp)$"))
{
<img src="@(ResourceService.BucketItem("tickets", message.AttachmentUrl))" class="img-fluid" alt="Attachment"/>
}
else
{
<a class="btn btn-secondary" href="@(ResourceService.BucketItem("tickets", message.AttachmentUrl))">
<i class="me-2 bx bx-download"></i> @(message.AttachmentUrl)
</a>
}
</div>
}
</div>
</div>
</div>
}
else
{
<div class="d-flex justify-content-start mb-10 ">
<div class="d-flex flex-column align-items-start">
<div class="d-flex align-items-center mb-2">
<div class="symbol symbol-35px symbol-circle ">
@if (message.Sender != null)
{
<img alt="Avatar" src="@(ResourceService.Avatar(message.Sender))">
}
</div>
<div class="ms-3">
<span class="fs-5 fw-bold text-gray-900 text-hover-primary me-1">@(message.Sender!.FirstName) @(message.Sender!.LastName)</span>
<span class="text-muted fs-7 mb-1">@(Formatter.FormatAgoFromDateTime(message.CreatedAt, SmartTranslateService))</span>
</div>
</div>
<div class="p-5 rounded bg-light-info text-dark fw-semibold mw-lg-400px text-start">
@{
int i = 0;
var arr = message.Content.Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
}
@foreach (var line in arr)
{
@line
if (i++ != arr.Length - 1)
{
<br/>
}
}
@if (!string.IsNullOrEmpty(message.AttachmentUrl))
{
<div class="mt-3">
@if (Regex.IsMatch(message.AttachmentUrl, @"\.(jpg|jpeg|png|gif|bmp)$"))
{
<img src="@(ResourceService.BucketItem("tickets", message.AttachmentUrl))" class="img-fluid" alt="Attachment"/>
}
else
{
<a class="btn btn-secondary" href="@(ResourceService.BucketItem("tickets", message.AttachmentUrl))">
<i class="me-2 bx bx-download"></i> @(message.AttachmentUrl)
</a>
}
</div>
}
</div>
</div>
</div>
}
}
else if (message.IsSystemMessage)
{
<div class="separator separator-content border-primary my-15">
<span class="w-250px fw-bold">
@(message.Content)
</span>
</div>
}
else
{
if (ViewAsSupport)
{
<div class="d-flex justify-content-start mb-10 ">
<div class="d-flex flex-column align-items-start">
<div class="d-flex align-items-center mb-2">
<div class="symbol symbol-35px symbol-circle ">
@if (message.Sender != null)
{
<img alt="Avatar" src="@(ResourceService.Avatar(message.Sender))">
}
</div>
<div class="ms-3">
<span class="fs-5 fw-bold text-gray-900 text-hover-primary me-1">@(message.Sender!.FirstName) @(message.Sender!.LastName)</span>
<span class="text-muted fs-7 mb-1">@(Formatter.FormatAgoFromDateTime(message.CreatedAt, SmartTranslateService))</span>
</div>
</div>
<div class="p-5 rounded bg-light-info text-dark fw-semibold mw-lg-400px text-start">
@{
int i = 0;
var arr = message.Content.Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
}
@foreach (var line in arr)
{
@line
if (i++ != arr.Length - 1)
{
<br/>
}
}
@if (!string.IsNullOrEmpty(message.AttachmentUrl))
{
<div class="mt-3">
@if (Regex.IsMatch(message.AttachmentUrl, @"\.(jpg|jpeg|png|gif|bmp)$"))
{
<img src="@(ResourceService.BucketItem("tickets", message.AttachmentUrl))" class="img-fluid" alt="Attachment"/>
}
else
{
<a class="btn btn-secondary" href="@(ResourceService.BucketItem("tickets", message.AttachmentUrl))">
<i class="me-2 bx bx-download"></i> @(message.AttachmentUrl)
</a>
}
</div>
}
</div>
</div>
</div>
}
else
{
<div class="d-flex justify-content-end mb-10 ">
<div class="d-flex flex-column align-items-end">
<div class="d-flex align-items-center mb-2">
<div class="me-3">
<span class="text-muted fs-7 mb-1">@(Formatter.FormatAgoFromDateTime(message.CreatedAt, SmartTranslateService))</span>
<span class="fs-5 fw-bold text-gray-900 text-hover-primary ms-1">@(message.Sender!.FirstName) @(message.Sender!.LastName)</span>
</div>
<div class="symbol symbol-35px symbol-circle ">
@if (message.Sender != null)
{
<img alt="Avatar" src="@(ResourceService.Avatar(message.Sender))">
}
</div>
</div>
<div class="p-5 rounded bg-light-primary text-dark fw-semibold mw-lg-400px text-end">
@{
int i = 0;
var arr = message.Content.Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
}
@foreach (var line in arr)
{
@line
if (i++ != arr.Length - 1)
{
<br/>
}
}
@if (!string.IsNullOrEmpty(message.AttachmentUrl))
{
<div class="mt-3">
@if (Regex.IsMatch(message.AttachmentUrl, @"\.(jpg|jpeg|png|gif|bmp)$"))
{
<img src="@(ResourceService.BucketItem("tickets", message.AttachmentUrl))" class="img-fluid" alt="Attachment"/>
}
else
{
<a class="btn btn-secondary" href="@(ResourceService.BucketItem("tickets", message.AttachmentUrl))">
<i class="me-2 bx bx-download"></i> @(message.AttachmentUrl)
</a>
}
</div>
}
</div>
</div>
</div>
}
}
}
</div>
@code
{
[Parameter]
public IEnumerable<TicketMessage> Messages { get; set; }
[Parameter]
public bool ViewAsSupport { get; set; }
}

View File

@@ -1,65 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject WebSpaceService WebSpaceService
@inject SmartTranslateService SmartTranslateService
@inject AlertService AlertService
<div class="row gy-5 g-xl-10">
<div class="col-xl-4 mb-xl-10">
<div class="card h-md-100">
<div class="card-body d-flex flex-column flex-center">
<div class="position-relative" style="width: 100%; height: 0; padding-bottom: 56.25%;">
<div class="position-absolute top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center">
<img class="img-fluid" src="/assets/media/gif/loading.gif" alt="Placeholder" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;">
<div class="position-absolute top-0 start-0 w-100 h-100" style="background-image: url('https://shs.moonlightpanel.xyz/api/screenshot?url=http://@(CurrentWebSpace.Domain)'); background-size: cover; background-position: center;"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-8 mb-5 mb-xl-10">
<div class="card card-flush h-xl-100">
<div class="card-body pt-2">
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="row mt-5">
<div class="card border">
<div class="card-header">
<span class="card-title">
<TL>SSL certificates</TL>
</span>
<div class="card-toolbar">
<WButton Text="@(SmartTranslateService.Translate("Issue certificate"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn-success"
OnClick="IssueCertificate">
</WButton>
</div>
</div>
</div>
</div>
</LazyLoader>
</div>
</div>
</div>
</div>
@code
{
[CascadingParameter]
public WebSpace CurrentWebSpace { get; set; }
private LazyLoader LazyLoader;
private Task Load(LazyLoader lazyLoader)
{
return Task.CompletedTask;
}
private async Task IssueCertificate()
{
await WebSpaceService.IssueSslCertificate(CurrentWebSpace);
await AlertService.Success(SmartTranslateService.Translate("Lets Encrypt certificate successfully issued"));
}
}

View File

@@ -1,137 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Forms
@using Moonlight.App.Services
@inject SmartTranslateService SmartTranslateService
@inject WebSpaceService WebSpaceService
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card w-100 mb-4">
<div class="card-body py-2">
<div class="my-4 w-100">
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
<div class="input-group">
<InputText @bind-Value="Model.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Name"))"></InputText>
<InputText @bind-Value="Model.Password" type="password" class="form-control" placeholder="@(SmartTranslateService.Translate("Password"))"></InputText>
<button class="btn btn-primary" type="submit">
<TL>Create</TL>
</button>
</div>
</SmartForm>
</div>
</div>
</div>
@if (Databases.Any())
{
@foreach (var databases in Databases.Chunk(2))
{
<div class="row">
@foreach (var database in databases)
{
<div class="col-sm-6">
<div class="card">
<div class="card-header">
<div class="card-title">
<span>@(database.UserName) - @(database.UserName.ToUpper().Replace("MYSQL", "MySQL"))</span>
</div>
</div>
<div class="card-body me-1">
<table class="w-100 table-layout-fixed">
<tr>
<td class="w-25">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Host</TL>
</label>
</td>
<td class="pb-2">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(CurrentWebSpace.CloudPanel.Host)">
</td>
</tr>
<tr>
<td>
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Port</TL>
</label>
</td>
<td class="pb-2">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="3306">
</td>
</tr>
<tr>
<td>
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Username</TL>
</label>
</td>
<td class="pb-2">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(database.UserName)">
</td>
</tr>
<tr>
<td>
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Password</TL>
</label>
</td>
<td class="pb-2">
<input type="text" class="form-control form-control-solid disabled blur-unless-hover" disabled="disabled" value="@(database.Password)">
</td>
</tr>
<tr>
<td>
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Database</TL>
</label>
</td>
<td class="pb-2">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(database.UserName)">
</td>
</tr>
</table>
<div class="text-end mt-2">
<DeleteButton Confirm="true" OnClick="() => DeleteDatabase(database)"/>
</div>
</div>
</div>
</div>
}
</div>
}
}
else
{
<div class="alert alert-warning">
<TL>No databases found for this webspace</TL>
</div>
}
</LazyLoader>
@code
{
[CascadingParameter]
public WebSpace CurrentWebSpace { get; set; }
private LazyLoader LazyLoader;
private MySqlDatabase[] Databases;
private DatabaseDataModel Model = new();
private async Task Load(LazyLoader arg)
{
Databases = await WebSpaceService.GetDatabases(CurrentWebSpace);
}
private async Task OnValidSubmit()
{
await WebSpaceService.CreateDatabase(CurrentWebSpace, Model.Name, Model.Password);
Model = new();
await LazyLoader.Reload();
}
private async Task DeleteDatabase(MySqlDatabase database)
{
await WebSpaceService.DeleteDatabase(CurrentWebSpace, database);
await LazyLoader.Reload();
}
}

View File

@@ -1,24 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services
@using Moonlight.Shared.Components.FileManagerPartials
@inject WebSpaceService WebSpaceService
<LazyLoader Load="Load">
<FileManager Access="Access">
</FileManager>
</LazyLoader>
@code
{
[CascadingParameter]
public WebSpace CurrentWebSpace { get; set; }
private FileAccess Access;
private async Task Load(LazyLoader arg)
{
Access = await WebSpaceService.CreateFileAccess(CurrentWebSpace);
}
}

View File

@@ -1,69 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Plugin.UI.Webspaces
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject SmartTranslateService SmartTranslateService
@inject WebSpaceService WebSpaceService
@inject NavigationManager NavigationManager
@inject AlertService AlertService
<div class="card card-body">
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex">
<div class="symbol symbol-circle me-5">
<div class="symbol-label bg-transparent text-primary border border-secondary border-dashed">
<i class="bx bx-globe bx-md"></i>
</div>
</div>
<div class="d-flex flex-column">
<div class="mb-1 fs-4">@(WebSpace.Domain)</div>
<div class="text-muted fs-5">@(WebSpace.CloudPanel.Name)</div>
</div>
</div>
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
OnClick="Delete"
CssClasses="btn-danger">
</WButton>
</div>
</div>
</div>
<div class="my-5"></div>
<div class="card mb-xl-10 mb-5">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
@foreach (var tab in Context.Tabs)
{
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Route == tab.Route ? "active" : "")" href="/webspace/@(WebSpace.Id + tab.Route)">
<TL>@(tab.Name)</TL>
</a>
</li>
}
</ul>
</div>
</div>
@code
{
[Parameter]
public string Route { get; set; }
[Parameter]
public WebSpace WebSpace { get; set; }
[CascadingParameter]
public WebspacePageContext Context { get; set; }
private async Task Delete()
{
if (await AlertService.ConfirmMath())
{
await WebSpaceService.Delete(WebSpace);
NavigationManager.NavigateTo("/webspaces");
}
}
}

View File

@@ -1,55 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@inject WebSpaceService WebSpaceService
<div class="card card-flush h-xl-100">
<div class="card-body pt-2">
<div class="mt-7 row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Sftp Host</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(CurrentWebSpace.CloudPanel.Host)">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Sftp Port</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="22">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Sftp Username</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(CurrentWebSpace.UserName)">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Sftp Password</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled blur-unless-hover" disabled="disabled" value="@(CurrentWebSpace.Password)">
</div>
</div>
</div>
</div>
@code
{
[CascadingParameter]
public WebSpace CurrentWebSpace { get; set; }
}

View File

@@ -1,77 +0,0 @@
@using XtermBlazor
@implements IDisposable
<Xterm
@ref="Xterm"
Options="TerminalOptions"
AddonIds="@(new[] { "xterm-addon-fit" })"
OnFirstRender="OnFirstRender">
</Xterm>
@code
{
private Xterm Xterm;
[Parameter]
public Action RunOnFirstRender { get; set; }
private TerminalOptions TerminalOptions = new()
{
CursorBlink = false,
CursorStyle = CursorStyle.Underline,
CursorWidth = 1,
DisableStdin = true,
FontFamily = "monospace"
};
public async Task WriteLine(string message)
{
try
{
await Xterm.WriteLine(message);
}
catch (Exception)
{
// ignored
}
}
public async void Dispose()
{
try
{
await Xterm.DisposeAsync();
}
catch (Exception)
{
// ignore dispose errors. They occur when the tab closes unexpectedly
// so we can ignore them
}
}
private async void OnFirstRender()
{
try
{
await Xterm.InvokeAddonFunctionVoidAsync("xterm-addon-fit", "fit");
RunOnFirstRender.Invoke();
await Task.Run(async () =>
{
try
{
await Task.Delay(1000);
await Xterm.InvokeAddonFunctionVoidAsync("xterm-addon-fit", "fit");
await Task.Delay(1000);
await Xterm.InvokeAddonFunctionVoidAsync("xterm-addon-fit", "fit");
}
catch (Exception){}
});
}
catch (Exception)
{
// ignored
}
}
}

View File

@@ -1,284 +0,0 @@
@using Moonlight.App.Services.Files
@using Moonlight.App.Services.Sessions
@inject ResourceService ResourceService
@inject DynamicBackgroundService DynamicBackgroundService
@inject IdentityService IdentityService
<div class="d-flex flex-column flex-root app-root">
<div class="app-page flex-column flex-column-fluid">
<!-- Page Header -->
<div class="app-header">
<div class="app-container container-fluid d-flex align-items-stretch justify-content-between">
<div class="d-flex align-items-center d-lg-none ms-n2 me-2" title="Show sidebar menu">
<a class="btn btn-icon btn-active-color-primary w-35px h-35px" @onclick:preventDefault @onclick="ToggleMobileSidebar">
<i class="bx bx-menu bx-md"></i>
</a>
</div>
@if (ShowMobileSidebar)
{
<div style="z-index: 105;" class="drawer-overlay" @onclick="ToggleMobileSidebar"></div>
}
<div class="d-flex align-items-center flex-grow-1 flex-lg-grow-0">
<a href="/" class="d-lg-none">
<img alt="Logo" src="@(ResourceService.Image("logo.svg"))" class="h-30px"/>
</a>
</div>
<div class="d-flex align-items-stretch justify-content-between flex-lg-grow-1">
<div class="app-header-menu app-header-mobile-drawer align-items-stretch">
<div class="menu menu-rounded menu-column menu-lg-row my-5 my-lg-0 align-items-stretch fw-semibold px-2 px-lg-0">
</div>
</div>
<Navbar></Navbar>
</div>
</div>
</div>
<!-- Page Header End --->
<div class="app-wrapper flex-column flex-row-fluid">
<!-- Sidebar -->
<div class="app-sidebar flex-column @(ShowMobileSidebar ? "drawer drawer-start drawer-on" : "")">
<div class="app-sidebar-logo px-6">
<a href="@(IdentityService.User != null ? "/" : "/login")">
<img alt="Logo" src="@(ResourceService.Image("logolong.png"))" class="h-45px app-sidebar-logo-default"/>
<img alt="Logo" src="@(ResourceService.Image("logo.svg"))" class="h-20px app-sidebar-logo-minimize"/>
</a>
</div>
<div class="app-sidebar-menu overflow-hidden flex-column-fluid">
<div class="app-sidebar-wrapper hover-scroll-overlay-y my-5">
<div class="menu menu-column menu-rounded menu-sub-indention px-3">
@if (IdentityService.User == null)
{
<div class="menu-item">
<a class="menu-link" href="/login">
<span class="menu-icon">
<i class="bx bxs-log-in"></i>
</span>
<span class="menu-title">
<TL>Login</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/register">
<span class="menu-icon">
<i class="bx bx-user-plus"></i>
</span>
<span class="menu-title">
<TL>Register</TL>
</span>
</a>
</div>
}
else
{
<div class="menu-item">
<a class="menu-link" href="/">
<span class="menu-icon">
<i class="bx bx-layer"></i>
</span>
<span class="menu-title">
<TL>Dashboard</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/servers">
<span class="menu-icon">
<i class="bx bx-server"></i>
</span>
<span class="menu-title">
<TL>Servers</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/webspaces">
<span class="menu-icon">
<i class="bx bx-globe"></i>
</span>
<span class="menu-title">
<TL>Webspaces</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/domains">
<span class="menu-icon">
<i class="bx bx-purchase-tag"></i>
</span>
<span class="menu-title">
<TL>Domains</TL>
</span>
</a>
</div>
if (IdentityService.Permissions.HasAnyPermissions())
{
<div class="menu-item pt-5">
<div class="menu-content">
<span class="menu-heading fw-bold text-uppercase fs-7">
<TL>Admin</TL>
</span>
</div>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin">
<span class="menu-icon">
<i class="bx bx-layer"></i>
</span>
<span class="menu-title">
<TL>Dashboard</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/system">
<span class="menu-icon">
<i class="bx bx-chip"></i>
</span>
<span class="menu-title">
<TL>System</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/security">
<span class="menu-icon">
<i class="bx bx-shield"></i>
</span>
<span class="menu-title">
<TL>Security</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/servers">
<span class="menu-icon">
<i class="bx bx-server"></i>
</span>
<span class="menu-title">
<TL>Servers</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/webspaces">
<span class="menu-icon">
<i class="bx bx-globe"></i>
</span>
<span class="menu-title">
<TL>Webspaces</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/users">
<span class="menu-icon">
<i class="bx bx-user"></i>
</span>
<span class="menu-title">
<TL>Users</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/domains">
<span class="menu-icon">
<i class="bx bx-purchase-tag"></i>
</span>
<span class="menu-title">
<TL>Domains</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/support">
<span class="menu-icon">
<i class="bx bx-support"></i>
</span>
<span class="menu-title">
<TL>Support</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/subscriptions">
<span class="menu-icon">
<i class="bx bx-credit-card"></i>
</span>
<span class="menu-title">
<TL>Subscriptions</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/statistics">
<span class="menu-icon">
<i class="bx bx-objects-vertical-bottom"></i>
</span>
<span class="menu-title">
<TL>Statistics</TL>
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/changelog">
<span class="menu-icon">
<i class="bx bx-notepad"></i>
</span>
<span class="menu-title">
<TL>Changelog</TL>
</span>
</a>
</div>
}
}
</div>
</div>
</div>
<div class="app-sidebar-footer flex-column-auto pt-2 pb-6 px-6">
<a href="/support" class="btn btn-flex flex-center btn-custom btn-primary overflow-hidden text-nowrap px-0 h-40px w-100 btn-label">
<i class="bx bx-sm bx-support"></i>
</a>
</div>
</div>
<!-- Sidebar End -->
<div class="app-main flex-column flex-row-fluid">
<div class="d-flex flex-column flex-column-fluid">
<div class="app-content flex-column-fluid" style="background-position: center; background-size: cover; background-repeat: no-repeat; background-attachment: fixed; background-image: linear-gradient(rgba(0, 0, 0, 0.55),rgba(0, 0, 0, 0.55)) ,url('@(DynamicBackgroundService.BackgroundImageUrl)');">
<div class="app-container container-fluid">
<div class="mt-10">
@ChildContent
</div>
</div>
</div>
</div>
<Footer></Footer>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public RenderFragment ChildContent { get; set; }
private bool ShowMobileSidebar = false;
private async Task ToggleMobileSidebar()
{
ShowMobileSidebar = !ShowMobileSidebar;
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,227 +0,0 @@
@using Moonlight.Shared.Components.ErrorBoundaries
@using Moonlight.Shared.Components.Auth
@using Moonlight.App.Database.Entities
@using Moonlight.App.Extensions
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions
@using Moonlight.App.Events
@implements IDisposable
@inherits LayoutComponentBase
@inject IJSRuntime JsRuntime
@inject IdentityService IdentityService
@inject SessionClientService SessionClientService
@inject NavigationManager NavigationManager
@inject EventSystem Event
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService
@inject IpBanService IpBanService
@inject DynamicBackgroundService DynamicBackgroundService
@inject KeyListenerService KeyListenerService
@inject ConfigService ConfigService
@inject IpVerificationService IpVerificationService
@{
var uri = new Uri(NavigationManager.Uri);
var pathParts = uri.LocalPath.Split("/").Reverse();
var title = "";
foreach (var pathPart in pathParts)
{
if (!string.IsNullOrEmpty(pathPart))
{
if (pathPart == pathParts.Last())
title += $"{pathPart.FirstCharToUpper()} ";
else
title += $"{pathPart.FirstCharToUpper()} - ";
}
}
}
<GlobalErrorBoundary>
<PageTitle>@(string.IsNullOrEmpty(title) ? "Dashboard - " : title)Moonlight</PageTitle>
<DefaultLayout>
<SoftErrorBoundary>
@if (UserProcessed)
{
if (IsIpBanned)
{
<div class="modal d-block">
<div class="modal-dialog modal-dialog-centered mw-900px">
<div class="modal-content">
<div class="pt-2 modal-body py-lg-10 px-lg-10">
<h2>@(SmartTranslateService.Translate("Your ip has been banned"))</h2>
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Your ip address has been banned by an admin"))</p>
</div>
</div>
</div>
</div>
}
else if (IsIpSuspicious)
{
<div class="modal d-block">
<div class="modal-dialog modal-dialog-centered mw-900px">
<div class="modal-content">
<div class="pt-2 modal-body py-lg-10 px-lg-10">
<h2>@(SmartTranslateService.Translate("Your ip his blocked. VPNs and Datacenter IPs are prohibited from accessing this site"))</h2>
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Please disable your vpn or proxy and try it again"))</p>
</div>
</div>
</div>
</div>
}
else
{
if (uri.LocalPath != "/login" &&
uri.LocalPath != "/passwordreset" &&
uri.LocalPath != "/register")
{
if (IdentityService.User == null)
{
<Login></Login>
}
else
{
if (IdentityService.User.Status == UserStatus.Banned)
{
<BannedAlert></BannedAlert>
}
else if (IdentityService.User.Status == UserStatus.Disabled)
{
<DisabledAlert></DisabledAlert>
}
else if (IdentityService.User.Status == UserStatus.PasswordPending)
{
<PasswordChangeView></PasswordChangeView>
}
else if (IdentityService.User.Status == UserStatus.DataPending)
{
<UserDataSetView></UserDataSetView>
}
else
{
<RenderPermissionChecker>
@Body
</RenderPermissionChecker>
<RatingPopup/>
}
}
}
else
{
if (uri.LocalPath == "/login")
{
<Login></Login>
}
else if (uri.LocalPath == "/register")
{
<Register></Register>
}
else if (uri.LocalPath == "/passwordreset")
{
<PasswordReset></PasswordReset>
}
}
}
}
else
{
<div class="modal d-block">
<div class="modal-dialog modal-dialog-centered mw-900px">
<div class="modal-content">
<div class="pt-2 modal-body py-lg-10 px-lg-10">
<h2>@(SmartTranslateService.Translate("Authenticating"))...</h2>
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Verifying token, loading user data"))</p>
</div>
</div>
</div>
</div>
}
</SoftErrorBoundary>
</DefaultLayout>
</GlobalErrorBoundary>
@code
{
private bool UserProcessed = false;
private bool IsIpBanned = false;
private bool IsIpSuspicious = false;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
try
{
DynamicBackgroundService.OnBackgroundImageChanged += async (_, _) => { await InvokeAsync(StateHasChanged); };
await Event.On<Object>("ipBan.update", this, async _ =>
{
IsIpBanned = await IpBanService.IsBanned();
NavigationManager.NavigateTo(NavigationManager.Uri, true);
});
await IdentityService.Load();
IsIpBanned = await IpBanService.IsBanned();
IsIpSuspicious = await IpVerificationService.IsDatacenterOrVpn(IdentityService.Ip);
UserProcessed = true;
await InvokeAsync(StateHasChanged);
await SessionClientService.Start();
NavigationManager.LocationChanged += async (_, _) =>
{
if (!NavigationManager.Uri.Contains("/server/"))
await DynamicBackgroundService.Reset();
};
if (IdentityService.User != null)
{
await Event.On<SupportChatMessage>(
$"supportChat.{IdentityService.User.Id}.message",
this,
async message =>
{
if (!NavigationManager.Uri.EndsWith("/support") && message.Sender != IdentityService.User)
{
await ToastService.Info($"Support: {message.Content}");
}
});
}
await KeyListenerService.Initialize();
if (ConfigService.Get().Moonlight.EnableLatencyCheck)
{
await JsRuntime.InvokeVoidAsync("moonlight.loading.checkConnection",
ConfigService.Get().Moonlight.AppUrl,
ConfigService.Get().Moonlight.LatencyCheckThreshold);
}
}
catch (Exception)
{
// ignored
}
}
}
public async void Dispose()
{
await SessionClientService.Stop();
await KeyListenerService.DisposeAsync();
if (IdentityService.User != null)
{
await Event.Off($"supportChat.{IdentityService.User.Id}.message", this);
}
}
}

View File

@@ -1,129 +0,0 @@
@using Moonlight.Shared.Components.ErrorBoundaries
@using Moonlight.App.Database.Entities
@using Moonlight.App.Extensions
@using Moonlight.App.Services.Sessions
@implements IDisposable
@inherits LayoutComponentBase
@inject IJSRuntime JsRuntime
@inject IdentityService IdentityService
@inject SessionClientService SessionClientService
@inject NavigationManager NavigationManager
<GlobalErrorBoundary>
@{
var uri = new Uri(NavigationManager.Uri);
var pathParts = uri.LocalPath.Split("/");
var title = "";
foreach (var pathPart in pathParts)
{
if (!string.IsNullOrEmpty(pathPart))
title += $"{pathPart.FirstCharToUpper()} ";
}
}
<PageTitle>@(string.IsNullOrEmpty(title) ? "Dashboard " : title)- Moonlight</PageTitle>
<div class="d-flex flex-column flex-root app-root" id="kt_app_root">
<div class="app-page flex-column flex-column-fluid" id="kt_app_page">
<canvas id="snow" class="snow-canvas"></canvas>
@{
//TODO: Add a way to disable the snow
}
<PageHeader></PageHeader>
<div class="app-wrapper flex-column flex-row-fluid" id="kt_app_wrapper">
<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_container" class="app-container container-fluid">
<div class="mt-10">
<PageErrorBoundary>
@Body
</PageErrorBoundary>
</div>
</div>
</div>
</div>
<Footer></Footer>
</div>
</div>
</div>
</div>
</GlobalErrorBoundary>
@code
{
protected override void OnInitialized()
{
AddBodyAttribute("data-kt-app-page-loading", "on");
}
protected override void OnAfterRender(bool firstRender)
{
//Initialize classes and attributes for layout with dark sidebar
AddBodyAttribute("data-kt-app-reset-transition", "true");
AddBodyAttribute("data-kt-app-layout", "dark-sidebar");
AddBodyAttribute("data-kt-app-header-fixed", "true");
AddBodyAttribute("data-kt-app-sidebar-fixed", "true");
AddBodyAttribute("data-kt-app-sidebar-hoverable", "true");
AddBodyAttribute("data-kt-app-sidebar-push-header", "true");
AddBodyAttribute("data-kt-app-sidebar-push-toolbar", "true");
AddBodyAttribute("data-kt-app-sidebar-push-footer", "true");
AddBodyAttribute("data-kt-app-toolbar-enabled", "true");
AddBodyClass("app-default");
JsRuntime.InvokeVoidAsync("KTModalUpgradePlan.init");
JsRuntime.InvokeVoidAsync("KTCreateApp.init");
JsRuntime.InvokeVoidAsync("KTModalUserSearch.init");
JsRuntime.InvokeVoidAsync("KTModalNewTarget.init");
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
try
{
await IdentityService.Load();
await InvokeAsync(StateHasChanged);
await Task.Delay(300);
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-reset-transition");
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-page-loading");
await JsRuntime.InvokeVoidAsync("KTMenu.createInstances");
await JsRuntime.InvokeVoidAsync("KTDrawer.createInstances");
await JsRuntime.InvokeVoidAsync("createSnow");
await SessionClientService.Start();
}
catch (Exception)
{
// ignored
}
}
}
public async void Dispose()
{
await SessionClientService.Stop();
}
private void AddBodyAttribute(string attribute, string value)
{
JsRuntime.InvokeVoidAsync("document.body.setAttribute", attribute, value);
}
private void AddBodyClass(string className)
{
JsRuntime.InvokeVoidAsync("document.body.classList.add", className);
}
}

View File

@@ -1,53 +0,0 @@
@page "/admin/changelog"
@using Moonlight.App.Services
@inject MoonlightService MoonlightService
@attribute [PermissionRequired(nameof(Permissions.AdminChangelog))]
@{
int i = 0;
}
<div class="card card-docs flex-row-fluid mb-2">
<div class="card-body fs-6 py-15 px-10 py-lg-15 px-lg-15 text-gray-700">
<div class="accordion accordion-flush accordion-icon-toggle" id="kt_accordion">
@foreach (var prt in MoonlightService.ChangeLog.ToArray().Reverse())
{
i++;
<div class="accordion-item mb-5">
<div class="accordion-header py-3 d-flex" data-bs-toggle="collapse" data-bs-target="#i@(i)" aria-expanded="@(i == 0)">
<span class="accordion-icon">
<span class="svg-icon svg-icon-3">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.5" x="18" y="13" width="13" height="2" rx="1" transform="rotate(-180 18 13)" fill="currentColor"></rect>
<path d="M15.4343 12.5657L11.25 16.75C10.8358 17.1642 10.8358 17.8358 11.25 18.25C11.6642 18.6642 12.3358 18.6642 12.75 18.25L18.2929 12.7071C18.6834 12.3166 18.6834 11.6834 18.2929 11.2929L12.75 5.75C12.3358 5.33579 11.6642 5.33579 11.25 5.75C10.8358 6.16421 10.8358 6.83579 11.25 7.25L15.4343 11.4343C15.7467 11.7467 15.7467 12.2533 15.4343 12.5657Z" fill="currentColor"></path>
</svg>
</span>
</span>
<h3 class="fs-2 text-gray-800 fw-bold mb-0 ms-4">@prt[0]</h3>
</div>
<div id="i@(i)" class="fs-6 mt-1 mb-1 py-0 ps-10 @(i == 0 ? "show" : "collapse")" data-bs-parent="#kt_accordion" style="">
<div class="accordion-body ps-0 pt-0">
<div class="mb-5">
@{
var o = prt[1..];
}
<h3 class="fs-6 fw-bold mb-1">Changes</h3>
<ul class="my-0 py-0">
@foreach (var v in o)
{
<li class="py-2">
@v
</li>
}
</ul>
</div>
</div>
</div>
</div>
}
</div>
</div>
</div>

View File

@@ -1,88 +0,0 @@
@page "/admin/domains"
@using Moonlight.App.Repositories.Domains
@using Moonlight.App.Database.Entities
@using Microsoft.EntityFrameworkCore
@using BlazorTable
@using Moonlight.App.Services
@using Moonlight.Shared.Components.Navigations
@inject DomainRepository DomainRepository
@inject DomainService DomainService
@inject SmartTranslateService SmartTranslateService
@attribute [PermissionRequired(nameof(Permissions.AdminDomains))]
<AdminDomainsNavigation Index="0" />
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
<span class="card-label fw-bold fs-3 mb-1">
<TL>Domains</TL>
</span>
</h3>
<div class="card-toolbar">
<a href="/admin/domains/new" class="btn btn-sm btn-light-success">
<i class="bx bx-layer-plus"></i>
<TL>New domain</TL>
</a>
</div>
</div>
<div class="card-body pt-0">
<div class="table-responsive">
<Table TableItem="Domain" Items="Domains" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Shared domain"))" Field="@(x => x.SharedDomain)" Sortable="true" Filterable="true" Width="10%">
<Template>
<span>@(context.SharedDomain.Name)</span>
</Template>
</Column>
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="10%">
<Template>
<a href="/admin/users/view/@(context.Owner.Id)">@(context.Owner.Email)</a>
</Template>
</Column>
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
<Template>
<a href="/domain/@(context.Id)">Manage</a>
</Template>
</Column>
<Column TableItem="Domain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
<Template>
<DeleteButton Confirm="true"
AdditionalCssClasses="float-end"
OnClick="() => Delete(context)">
</DeleteButton>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</div>
</div>
</LazyLoader>
@code
{
private Domain[] Domains;
private LazyLoader LazyLoader;
private Task Load(LazyLoader arg)
{
Domains = DomainRepository
.Get()
.Include(x => x.SharedDomain)
.Include(x => x.Owner)
.ToArray();
return Task.CompletedTask;
}
private async Task Delete(Domain context)
{
await DomainService.Delete(context);
await LazyLoader.Reload();
}
}

View File

@@ -1,74 +0,0 @@
@page "/admin/domains/new"
@using Moonlight.App.Services
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Forms
@using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Domains
@inject SmartTranslateService SmartTranslateService
@inject SharedDomainRepository SharedDomainRepository
@inject UserRepository UserRepository
@inject NavigationManager NavigationManager
@inject DomainService DomainService
@attribute [PermissionRequired(nameof(Permissions.AdminNewDomain))]
<div class="row mb-5">
<div class="card card-body p-10">
<LazyLoader Load="Load">
<SmartForm Model="Model" OnValidSubmit="Add">
<label class="form-label">
<TL>Domain name</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-purchase-tag-alt"></i>
</span>
<InputText @bind-Value="Model.Name" class="form-control" placeholder="@(SmartTranslateService.Translate("Domain name"))"></InputText>
</div>
<div class="mb-5">
<label class="form-label">
<TL>Shared domain</TL>
</label>
<SmartSelect @bind-Value="Model.SharedDomain"
Items="SharedDomains"
DisplayField="@(x => x.Name)">
</SmartSelect>
</div>
<div class="input-group mb-5">
<SmartDropdown @bind-Value="Model.Owner"
Items="Users"
DisplayFunc="@(x => x.Email)"
SearchProp="@(x => x.Email)">
</SmartDropdown>
</div>
<button class="btn btn-success" type="submit">
<TL>Create</TL>
</button>
</SmartForm>
</LazyLoader>
</div>
</div>
@code
{
private DomainDataModel Model = new();
private User[] Users;
private SharedDomain[] SharedDomains;
private Task Load(LazyLoader lazyLoader)
{
Users = UserRepository.Get().ToArray();
SharedDomains = SharedDomainRepository.Get().ToArray();
return Task.CompletedTask;
}
private async Task Add()
{
await DomainService.Create(Model.Name, Model.SharedDomain, Model.Owner);
NavigationManager.NavigateTo("/admin/domains");
}
}

View File

@@ -1,90 +0,0 @@
@page "/admin/domains/shared"
@using Moonlight.App.Repositories.Domains
@using Moonlight.App.Services
@using CloudFlare.Client.Api.Zones
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services.Interop
@using BlazorTable
@using Moonlight.Shared.Components.Navigations
@inject SharedDomainRepository SharedDomainRepository
@inject SmartTranslateService SmartTranslateService
@inject DomainService DomainService
@inject AlertService AlertService
@inject ToastService ToastService
@attribute [PermissionRequired(nameof(Permissions.AdminSharedDomains))]
<AdminDomainsNavigation Index="1" />
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
<span class="card-label fw-bold fs-3 mb-1">
<span>
<TL>Shared domains</TL>
</span>
</span>
</h3>
<div class="card-toolbar">
<a href="/admin/domains/shared/new" class="btn btn-sm btn-light-success">
<i class="bx bx-layer-plus"></i>
<span>
<TL>Add shared domain</TL>
</span>
</a>
</div>
</div>
<div class="card-body">
<Table TableItem="SharedDomain" Items="SharedDomains" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
<Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
<Column TableItem="SharedDomain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
<Template>
<DeleteButton Confirm="true"
AdditionalCssClasses="float-end"
OnClick="() => Delete(context)">
</DeleteButton>
</Template>
</Column>
</Table>
</div>
</div>
</LazyLoader>
@code
{
private string DomainId = "";
private Zone[] Zones;
private SharedDomain[] SharedDomains;
private LazyLoader LazyLoader;
private async Task Load(LazyLoader lazyLoader)
{
Zones = await DomainService.GetAvailableDomains();
SharedDomains = SharedDomainRepository.Get().ToArray();
}
private async Task Delete(SharedDomain sharedDomain)
{
try
{
SharedDomainRepository.Delete(sharedDomain);
await ToastService.Success(SmartTranslateService.Translate("Shared domain successfully deleted"));
await LazyLoader.Reload();
}
catch (Exception e)
{
//TODO: Add check if any domains are left
await AlertService.Error(
SmartTranslateService.Translate("Error"),
SmartTranslateService.Translate("Something went wrong. Are any domains associated with this shared domain still there?")
);
}
}
}

View File

@@ -1,85 +0,0 @@
@page "/admin/domains/shared/new"
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Moonlight.App.Repositories.Domains
@using CloudFlare.Client.Api.Zones
@using Moonlight.App.Database.Entities
@inject SharedDomainRepository SharedDomainRepository
@inject SmartTranslateService SmartTranslateService
@inject DomainService DomainService
@inject ToastService ToastService
@inject NavigationManager NavigationManager
@attribute [PermissionRequired(nameof(Permissions.AdminNewSharedDomain))]
<LazyLoader Load="Load" @ref="LazyLoader">
<div class="row mb-5">
<div class="card card-body">
<div class="mx-4 mt-4 mb-6">
<div class="mb-4">
<label class="form-label">
<TL>Domain</TL>
</label>
<select @bind="DomainId" class="form-select">
@if (Zones.Any())
{
foreach (var zone in Zones)
{
<option value="@(zone.Id)">@(zone.Name)</option>
}
}
else
{
<option value="">
<TL>No domains available</TL>
</option>
}
</select>
</div>
<div class="float-end">
<WButton Text="@(SmartTranslateService.Translate("Add"))"
WorkingText="@(SmartTranslateService.Translate("Adding"))"
CssClasses="btn-success float-end"
OnClick="Add">
</WButton>
</div>
</div>
</div>
</div>
</LazyLoader>
@code {
private string DomainId = "";
private Zone[] Zones;
private SharedDomain[] SharedDomains;
private LazyLoader LazyLoader;
private async Task Load(LazyLoader lazyLoader)
{
Zones = await DomainService.GetAvailableDomains();
SharedDomains = SharedDomainRepository.Get().ToArray();
}
private async Task Add()
{
if(string.IsNullOrEmpty(DomainId))
return;
var domain = Zones.First(x => x.Id == DomainId);
var sd = new SharedDomain()
{
CloudflareId = domain.Id,
Name = domain.Name
};
SharedDomainRepository.Add(sd);
await ToastService.Success(SmartTranslateService.Translate("Shared domain successfully added"));
NavigationManager.NavigateTo("/admin/domains/shared");
}
}

View File

@@ -1,170 +0,0 @@
@page "/admin"
@using Moonlight.App.Repositories.Servers
@using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Domains
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services
@using Newtonsoft.Json
@inject ServerRepository ServerRepository
@inject UserRepository UserRepository
@inject Repository<WebSpace> WebSpaceRepository
@inject DomainRepository DomainRepository
@inject ConfigService ConfigService
@attribute [PermissionRequired(nameof(Permissions.AdminDashboard))]
<LazyLoader Load="Load">
<div class="row mb-5">
<div class="col-12 col-lg-6 col-xl">
<a class="mt-4 card" href="/admin/servers">
<div class="card-body">
<div class="row align-items-center gx-0">
<div class="col">
<h6 class="text-uppercase text-muted mb-2">
<TL>Servers</TL>
</h6>
<span class="h2 mb-0">
@(ServerCount)
</span>
</div>
<div class="col-auto">
<span class="h2 text-muted mb-0">
<i class="text-primary bx bx-server bx-lg"></i>
</span>
</div>
</div>
</div>
</a>
</div>
<div class="col-12 col-lg-6 col-xl">
<a class="mt-4 card" href="/admin/webspaces">
<div class="card-body">
<div class="row align-items-center gx-0">
<div class="col">
<h6 class="text-uppercase text-muted mb-2">
<TL>Webspaces</TL>
</h6>
<span class="h2 mb-0">
@(WebSpaceCount)
</span>
</div>
<div class="col-auto">
<span class="h2 text-muted mb-0">
<i class="text-primary bx bx-globe bx-lg"></i>
</span>
</div>
</div>
</div>
</a>
</div>
<div class="col-12 col-lg-6 col-xl">
<a class="mt-4 card" href="/admin/domains">
<div class="card-body">
<div class="row align-items-center gx-0">
<div class="col">
<h6 class="text-uppercase text-muted mb-2">
<TL>Domains</TL>
</h6>
<span class="h2 mb-0">
@(DomainCount)
</span>
</div>
<div class="col-auto">
<span class="h2 text-muted mb-">
<i class="text-primary bx bx-purchase-tag bx-lg"></i>
</span>
</div>
</div>
</div>
</a>
</div>
<div class="col-12 col-lg-6 col-xl">
<a class="mt-4 card" href="/admin/users">
<div class="card-body">
<div class="row align-items-center gx-0">
<div class="col">
<h6 class="text-uppercase text-muted mb-2">
<TL>Users</TL>
</h6>
<span class="h2 mb-0">
@(UserCount)
</span>
</div>
<div class="col-auto">
<span class="h2 text-muted mb-">
<i class="text-primary bx bx-user bx-lg"></i>
</span>
</div>
</div>
</div>
</a>
</div>
</div>
<LazyLoader Load="LoadHealthCheckData">
@if (HealthCheckData == null)
{
<div class="card">
<div class="card-header">
<div class="card-title">
<TL>Moonlight health</TL>
</div>
</div>
<div class="card-body">
<div class="alert alert-warning">
<TL>Unable to fetch health check data</TL>
</div>
</div>
</div>
}
else
{
<HealthCheckView HealthCheck="@HealthCheckData"/>
}
</LazyLoader>
</LazyLoader>
@code
{
private int ServerCount = 0;
private int UserCount = 0;
private int DomainCount = 0;
private int WebSpaceCount = 0;
private HealthCheck? HealthCheckData;
private Task Load(LazyLoader lazyLoader)
{
ServerCount = ServerRepository.Get().Count();
UserCount = UserRepository.Get().Count();
DomainCount = DomainRepository.Get().Count();
WebSpaceCount = WebSpaceRepository.Get().Count();
return Task.CompletedTask;
}
private async Task LoadHealthCheckData(LazyLoader lazyLoader)
{
await lazyLoader.SetText("Loading health check data");
var appUrl = ConfigService
.Get()
.Moonlight.AppUrl;
try
{
using var client = new HttpClient();
var json = await client.GetStringAsync($"{appUrl}/_health");
HealthCheckData = JsonConvert.DeserializeObject<HealthCheck>(json) ?? new();
}
catch (Exception e)
{
HealthCheckData = null;
Logger.Warn("Unable to fetch health check data");
Logger.Warn(e);
}
}
}

View File

@@ -1,228 +0,0 @@
@page "/admin/nodes/edit/{id:int}"
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@using Microsoft.EntityFrameworkCore
@using BlazorTable
@inject NodeRepository NodeRepository
@inject SmartTranslateService SmartTranslateService
@inject NavigationManager NavigationManager
@attribute [PermissionRequired(nameof(Permissions.AdminNodeEdit))]
<LazyLoader Load="Load" @ref="LazyLoader">
@if (Node == null)
{
<div class="alert alert-warning">
<TL>No node with this id found</TL>
</div>
}
else
{
<div class="d-flex">
<div class="flex-column">
<div class="card rounded-3 w-md-550px">
<div class="card-body">
<div class="d-flex flex-center flex-column-fluid">
<div class="form w-100 fv-plugins-bootstrap5 fv-plugins-framework">
<div class="fv-row mb-8">
<label class="form-label">
<TL>Nodename</TL>
</label>
<input @bind="Node.Name" type="text" placeholder="@(SmartTranslateService.Translate("Nodename"))" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<label class="form-label">
<TL>FQDN</TL>
</label>
<input @bind="Node.Fqdn" type="text" placeholder="@(SmartTranslateService.Translate("FQDN"))" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<label class="form-label">
<TL>Token Id</TL>
</label>
<input @bind="Node.TokenId" type="text" placeholder="@(SmartTranslateService.Translate("Token Id"))" class="blur-unless-hover form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<label class="form-label">
<TL>Token</TL>
</label>
<input @bind="Node.Token" type="text" placeholder="@(SmartTranslateService.Translate("Token"))" class="blur-unless-hover form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<label class="form-label">
<TL>Http port</TL>
</label>
<input @bind="Node.HttpPort" type="number" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<label class="form-label">
<TL>Sftp port</TL>
</label>
<input @bind="Node.SftpPort" type="number" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<label class="form-label">
<TL>Moonlight daemon port</TL>
</label>
<input @bind="Node.MoonlightDaemonPort" type="number" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<div class="input-group">
<label class="col-lg-4 col-form-label fw-semibold fs-6">
<TL>SSL</TL>
</label>
<div class="col-lg-8 d-flex align-items-center">
<div class="form-check form-check-solid form-switch form-check-custom fv-row">
<input @bind="Node.Ssl" class="form-check-input w-45px h-30px" type="checkbox" id="ssl">
<label class="form-check-label" for="ssl"></label>
</div>
</div>
</div>
</div>
<div class="fv-row mb-9">
<WButton Text="@(SmartTranslateService.Translate("Save"))"
WorkingText="@(SmartTranslateService.Translate("Saving"))"
CssClasses="btn-success"
OnClick="Save">
</WButton>
<a href="/admin/nodes" class="btn btn-primary">
<TL>Back</TL>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="flex-column w-100">
<div class="ms-5 card card-body">
<div class="form w-100">
<div class="mb-8 row g-3">
<div class="col-auto">
<input @bind="Port" type="number" class="col-auto form-control bg-transparent">
</div>
<div class="col-auto">
<WButton Text="@(SmartTranslateService.Translate("Add"))"
WorkingText="@(SmartTranslateService.Translate("Adding"))"
CssClasses="col-auto btn-success"
OnClick="CreateAllocation">
</WButton>
</div>
<div class="col-auto">
<label class="form-label">
<TL>Start</TL>
</label>
<input @bind="StartPort" type="number" class="col-auto form-control bg-transparent">
</div>
<div class="col-auto">
<label class="form-label">
<TL>End</TL>
</label>
<input @bind="EndPort" type="number" class="col-auto form-control bg-transparent">
</div>
<div class="col-auto">
<WButton Text="@(SmartTranslateService.Translate("Add"))"
WorkingText="@(SmartTranslateService.Translate("Adding"))"
CssClasses="col-auto btn-success"
OnClick="CreateAllocationRange">
</WButton>
</div>
</div>
</div>
<Table TableItem="NodeAllocation" Items="Node.Allocations" PageSize="25" TableHeadClass="border-bottom border-gray-200 fs-6 text-gray-600 fw-bold bg-light bg-opacity-75">
<Column TableItem="NodeAllocation" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
<Column TableItem="NodeAllocation" Title="@(SmartTranslateService.Translate("Port"))" Field="@(x => x.Port)" Sortable="true" Filterable="true" Width="10%"/>
<Column TableItem="NodeAllocation" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="20%">
<Template>
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
CssClasses="btn-danger"
OnClick="() => DeleteAllocation(context)">
</WButton>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</div>
</div>
}
</LazyLoader>
@code
{
[Parameter]
public int Id { get; set; }
private Node? Node;
private LazyLoader LazyLoader;
private int Port = 2000;
private int StartPort = 2000;
private int EndPort = 3000;
private Task Load(LazyLoader arg)
{
Node = NodeRepository
.Get()
.Include(x => x.Allocations)
.FirstOrDefault(x => x.Id == Id);
return Task.CompletedTask;
}
private Task Save()
{
NodeRepository.Update(Node!);
NavigationManager.NavigateTo("/admin/nodes");
return Task.CompletedTask;
}
private async Task DeleteAllocation(NodeAllocation nodeAllocation)
{
//TODO: Check if a server is using the allocation
Node!.Allocations.Remove(nodeAllocation);
NodeRepository.Update(Node);
await LazyLoader.Reload();
}
private async Task CreateAllocation()
{
var nodeAllocation = new NodeAllocation()
{
Port = Port
};
Node!.Allocations.Add(nodeAllocation);
NodeRepository.Update(Node);
Port = 2000;
await LazyLoader.Reload();
}
private async Task CreateAllocationRange()
{
for (int i = StartPort; i < EndPort; i++)
{
var nodeAllocation = new NodeAllocation()
{
Port = i
};
Node!.Allocations.Add(nodeAllocation);
}
NodeRepository.Update(Node);
StartPort = 2000;
EndPort = 3000;
await LazyLoader.Reload();
}
}

View File

@@ -1,169 +0,0 @@
@page "/admin/nodes"
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using BlazorTable
@using Moonlight.App.ApiClients.Wings.Resources
@using Moonlight.App.Helpers
@inject NodeRepository NodeRepository
@inject AlertService AlertService
@inject NodeService NodeService
@inject SmartTranslateService SmartTranslateService
@attribute [PermissionRequired(nameof(Permissions.AdminNodes))]
<AdminServersNavigation Index="3" />
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
<span class="card-label fw-bold fs-3 mb-1">
<TL>Nodes</TL>
</span>
</h3>
<div class="card-toolbar">
<a href="/admin/nodes/new" class="btn btn-sm btn-light-success">
<i class="bx bx-layer-plus"></i>
<TL>New node</TL>
</a>
</div>
</div>
<div class="card-body pt-0">
@if (Nodes.Any())
{
<div class="table-responsive">
<Table TableItem="Node" Items="Nodes" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Id)" Sortable="true" Filterable="true">
<Template>
@{
var ss = StatusCache.ContainsKey(context) ? StatusCache[context] : null;
}
@if (ss == null)
{
<span class="text-danger">Offline</span>
}
else
{
<span class="text-success">Online (@(ss.Version))</span>
}
</Template>
</Column>
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
<Template>
<a href="/admin/nodes/view/@(context.Id)">@(context.Name)</a>
</Template>
</Column>
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Fqdn"))" Field="@(x => x.Fqdn)" Sortable="true" Filterable="true"/>
<Column TableItem="Node" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<a href="/admin/nodes/edit/@(context.Id)">
@(SmartTranslateService.Translate("Edit"))
</a>
</Template>
</Column>
<Column TableItem="Node" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<a href="/admin/nodes/setup/@(context.Id)">
@(SmartTranslateService.Translate("Setup"))
</a>
</Template>
</Column>
<Column TableItem="Node" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
CssClasses="btn-sm btn-danger"
OnClick="() => Delete(context)">
</WButton>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
}
else
{
<div class="alert alert-info">
<TL>No nodes found</TL>
</div>
}
</div>
</div>
</LazyLoader>
@code
{
private Node[] Nodes;
private LazyLoader LazyLoader;
private Dictionary<Node, SystemStatus?> StatusCache = new();
private Task Load(LazyLoader lazyLoader)
{
lock (StatusCache)
{
StatusCache.Clear();
}
Nodes = NodeRepository.Get().ToArray();
Task.Run(() =>
{
foreach (var node in Nodes)
{
Task.Run(async () =>
{
try
{
var ss = await NodeService.GetStatus(node);
lock (StatusCache)
{
StatusCache.Add(node, ss);
}
}
catch (Exception e)
{
Logger.Verbose($"Error fetching status for node '{node.Name}'");
Logger.Verbose(e);
}
await InvokeAsync(StateHasChanged);
});
}
});
return Task.CompletedTask;
}
private async Task Delete(Node node)
{
var b = await AlertService.YesNo(
SmartTranslateService.Translate("Delete this node?"),
SmartTranslateService.Translate("Do you really want to delete this node"),
SmartTranslateService.Translate("Yes"),
SmartTranslateService.Translate("No")
);
if (b)
{
if (node.Allocations.Any())
await AlertService.Error(
SmartTranslateService.Translate("Error"),
SmartTranslateService.Translate("Delete all allocations before deleting the node")
);
else
{
NodeRepository.Delete(node);
await LazyLoader.Reload();
}
}
}
}

View File

@@ -1,98 +0,0 @@
@page "/admin/nodes/new"
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@inject SmartTranslateService SmartTranslateService
@inject NodeRepository NodeRepository
@inject NavigationManager NavigationManager
@attribute [PermissionRequired(nameof(Permissions.AdminNewNode))]
<div class="d-flex flex-center">
<div class="card rounded-3 w-md-550px">
<div class="card-body">
<div class="d-flex flex-center flex-column-fluid">
<div class="form w-100 fv-plugins-bootstrap5 fv-plugins-framework">
<div class="fv-row mb-8">
<label class="form-label">
<TL>Nodename</TL>
</label>
<input @bind="NewNode.Name" type="text" placeholder="@(SmartTranslateService.Translate("Nodename"))" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<label class="form-label">
<TL>FQDN</TL>
</label>
<input @bind="NewNode.Fqdn" type="text" placeholder="@(SmartTranslateService.Translate("FQDN"))" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<label class="form-label">
<TL>Http port</TL>
</label>
<input @bind="NewNode.HttpPort" type="number" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<label class="form-label">
<TL>Sftp port</TL>
</label>
<input @bind="NewNode.SftpPort" type="number" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<label class="form-label">
<TL>Moonlight daemon port</TL>
</label>
<input @bind="NewNode.MoonlightDaemonPort" type="number" class="form-control bg-transparent">
</div>
<div class="fv-row mb-8">
<div class="input-group">
<label class="col-lg-4 col-form-label fw-semibold fs-6">
<TL>SSL</TL>
</label>
<div class="col-lg-8 d-flex align-items-center">
<div class="form-check form-check-solid form-switch form-check-custom fv-row">
<input @bind="NewNode.Ssl" class="form-check-input w-45px h-30px" type="checkbox" id="ssl">
<label class="form-check-label" for="ssl"></label>
</div>
</div>
</div>
</div>
<div class="fv-row mb-9">
<WButton Text="@(SmartTranslateService.Translate("Create"))"
WorkingText="@(SmartTranslateService.Translate("Creating"))"
CssClasses="btn-success"
OnClick="Create">
</WButton>
<a href="/admin/nodes" class="btn btn-primary">
<TL>Back</TL>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
@code
{
private Node NewNode = new()
{ // Default values
HttpPort = 8080,
SftpPort = 2022,
MoonlightDaemonPort = 8081,
Ssl = true
};
private Task Create()
{
NewNode.Token = StringHelper.GenerateString(65);
NewNode.TokenId = StringHelper.GenerateString(17);
NodeRepository.Add(NewNode);
NavigationManager.NavigateTo("/admin/nodes");
return Task.CompletedTask;
}
}

View File

@@ -1,157 +0,0 @@
@page "/admin/nodes/setup/{id:int}"
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@inject NodeRepository NodeRepository
@inject ConfigService ConfigService
@attribute [PermissionRequired(nameof(Permissions.AdminNodeSetup))]
@{
var appUrl = ConfigService.Get().Moonlight.AppUrl;
}
<LazyLoader Load="Load">
@if (Node == null)
{
<div class="alert alert-warning">
<TL>No node with this id found</TL>
</div>
}
else
{
<div class="">
<div class="card rounded-3">
<div class="card-body">
<p class="fs-5">
<TL>Before configuring this node, install the daemon</TL><br/>
<a href="https://docs.moonlightpanel.xyz/install-the-daemon">How to install the daemon</a><br/>
<TL>Open a ssh connection to your node and enter</TL><br/>
<span class="fw-bold">nano /etc/pterodactyl/config.yml</span><br/>
<TL>and paste the config below. Then press STRG+O and STRG+X to save</TL>
</p>
<p class="mb-5 bg-light">
debug: false<br>
app_name: Moonlight<br>
uuid: @(Guid.NewGuid())<br/>
token_id: @(Node.TokenId)<br/>
token: @(Node.Token)<br/>
api:<br/>
&nbsp;&nbsp;host: 0.0.0.0<br/>
&nbsp;&nbsp;port: @(Node.HttpPort)<br/>
&nbsp;&nbsp;ssl:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;enabled: @(Node.Ssl ? "true" : "false")<br/>
&nbsp;&nbsp;&nbsp;&nbsp;cert: /etc/letsencrypt/live/@(Node.Fqdn)/fullchain.pem<br/>
&nbsp;&nbsp;&nbsp;&nbsp;key: /etc/letsencrypt/live/@(Node.Fqdn)/privkey.pem<br/>
&nbsp;&nbsp;disable_remote_download: false<br/>
&nbsp;&nbsp;upload_limit: 100<br/>
&nbsp;&nbsp;trusted_proxies: []<br/>
system:<br/>
&nbsp;&nbsp;root_directory: /var/lib/pterodactyl<br/>
&nbsp;&nbsp;log_directory: /var/log/pterodactyl<br/>
&nbsp;&nbsp;data: /var/lib/pterodactyl/volumes<br/>
&nbsp;&nbsp;archive_directory: /var/lib/pterodactyl/archives<br/>
&nbsp;&nbsp;backup_directory: /var/lib/pterodactyl/backups<br/>
&nbsp;&nbsp;tmp_directory: /tmp/pterodactyl<br/>
&nbsp;&nbsp;username: pterodactyl<br/>
&nbsp;&nbsp;timezone: Europe/Berlin<br/>
&nbsp;&nbsp;user:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;rootless:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;enabled: false<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container_uid: 0<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container_gid: 0<br/>
&nbsp;&nbsp;&nbsp;&nbsp;uid: 999<br/>
&nbsp;&nbsp;&nbsp;&nbsp;gid: 999<br/>
&nbsp;&nbsp;disk_check_interval: 150<br/>
&nbsp;&nbsp;activity_send_interval: 60<br/>
&nbsp;&nbsp;activity_send_count: 100<br/>
&nbsp;&nbsp;check_permissions_on_boot: true<br/>
&nbsp;&nbsp;enable_log_rotate: true<br/>
&nbsp;&nbsp;websocket_log_count: 150<br/>
&nbsp;&nbsp;sftp:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;bind_address: 0.0.0.0<br/>
&nbsp;&nbsp;&nbsp;&nbsp;bind_port: @(Node.SftpPort)<br/>
&nbsp;&nbsp;&nbsp;&nbsp;read_only: false<br/>
&nbsp;&nbsp;crash_detection:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;enabled: true<br/>
&nbsp;&nbsp;&nbsp;&nbsp;detect_clean_exit_as_crash: true<br/>
&nbsp;&nbsp;&nbsp;&nbsp;timeout: 60<br/>
&nbsp;&nbsp;backups:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;write_limit: 0<br/>
&nbsp;&nbsp;&nbsp;&nbsp;compression_level: best_speed<br/>
&nbsp;&nbsp;transfers:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;download_limit: 0<br/>
docker:<br/>
&nbsp;&nbsp;network:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;interface: 172.18.0.1<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dns:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;- 1.1.1.1<br/>
&nbsp;&nbsp;&nbsp;&nbsp;- 1.0.0.1<br/>
&nbsp;&nbsp;&nbsp;&nbsp;name: pterodactyl_nw<br/>
&nbsp;&nbsp;&nbsp;&nbsp;ispn: false<br/>
&nbsp;&nbsp;&nbsp;&nbsp;driver: bridge<br/>
&nbsp;&nbsp;&nbsp;&nbsp;network_mode: pterodactyl_nw<br/>
&nbsp;&nbsp;&nbsp;&nbsp;is_internal: false<br/>
&nbsp;&nbsp;&nbsp;&nbsp;enable_icc: true<br/>
&nbsp;&nbsp;&nbsp;&nbsp;network_mtu: 1500<br/>
&nbsp;&nbsp;&nbsp;&nbsp;interfaces:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v4:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;subnet: 172.18.0.0/16<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gateway: 172.18.0.1<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v6:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;subnet: fdba:17c8:6c94::/64<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gateway: fdba:17c8:6c94::1011<br/>
&nbsp;&nbsp;domainname: ""<br/>
&nbsp;&nbsp;registries: {}<br/>
&nbsp;&nbsp;tmpfs_size: 100<br/>
&nbsp;&nbsp;container_pid_limit: 512<br/>
&nbsp;&nbsp;installer_limits:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;memory: 1024<br/>
&nbsp;&nbsp;&nbsp;&nbsp;cpu: 100<br/>
&nbsp;&nbsp;overhead:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;override: false<br/>
&nbsp;&nbsp;&nbsp;&nbsp;default_multiplier: 1.05<br/>
&nbsp;&nbsp;&nbsp;&nbsp;multipliers: {}<br/>
&nbsp;&nbsp;use_performant_inspect: true<br/>
&nbsp;&nbsp;userns_mode: ""<br/>
&nbsp;&nbsp;log_config:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;type: local<br/>
&nbsp;&nbsp;&nbsp;&nbsp;config:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;compress: "false"<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;max-file: "1"<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;max-size: 5m<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mode: non-blocking<br/>
throttles:<br/>
&nbsp;&nbsp;enabled: true<br/>
&nbsp;&nbsp;lines: 2000<br/>
&nbsp;&nbsp;line_reset_interval: 100<br/>
remote: @(appUrl)<br/>
remote_query:<br/>
&nbsp;&nbsp;timeout: 30<br/>
&nbsp;&nbsp;boot_servers_per_page: 50<br/>
allowed_mounts: []<br/>
allowed_origins:<br/>
- '*'
</p>
<a href="/admin/nodes" class="btn btn-primary">
<TL>Back</TL>
</a>
</div>
</div>
</div>
}
</LazyLoader>
@code
{
[Parameter]
public int Id { get; set; }
private Node? Node;
private async Task Load(LazyLoader arg)
{
Node = NodeRepository.Get().FirstOrDefault(x => x.Id == Id);
}
}

View File

@@ -1,304 +0,0 @@
@page "/admin/nodes/view/{id:int}"
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.App.Services
@using Moonlight.App.ApiClients.Wings.Resources
@using Moonlight.App.ApiClients.Daemon.Resources
@inject NodeRepository NodeRepository
@inject NodeService NodeService
@attribute [PermissionRequired(nameof(Permissions.AdminNodeView))]
<LazyLoader Load="Load">
@if (Node == null)
{
<div class="alert alert-warning">
<TL>No node with this id found</TL>
</div>
}
else
{
<div class="d-flex flex-center">
<div class="row">
<div class="card">
<div class="card-header">
<h3 class="card-title">
<span class="fw-bold fs-3">
@(Node.Name) <TL>details</TL>
</span>
</h3>
</div>
<div class="card-body">
<div class="row g-3 g-lg-6">
<div class="col">
<div class="bg-gray-100 bg-opacity-70 rounded-2 px-6 py-5">
<div class="symbol symbol-30px me-5 mb-8">
<span class="symbol-label">
<i class="text-primary bx bx-lg bx-chip"></i>
</span>
</div>
<div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (CpuMetrics == null)
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
<span>
@(CpuMetrics.CpuUsage)% <TL>of CPU used</TL>
</span>
}
</span>
<span class="fw-semibold fs-6">
@if (CpuMetrics == null)
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
<span>@(CpuMetrics.CpuModel)</span>
}
</span>
</div>
</div>
</div>
<div class="col">
<div class="bg-gray-100 bg-opacity-70 rounded-2 px-6 py-5">
<div class="symbol symbol-30px me-5 mb-8">
<span class="symbol-label">
<i class="text-primary bx bx-lg bx-microchip"></i>
</span>
</div>
<div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (MemoryMetrics == null)
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
<span>
@(Formatter.FormatSize(ByteSizeValue.FromKiloBytes(MemoryMetrics.Used).Bytes)) <TL>of</TL> @(Formatter.FormatSize(ByteSizeValue.FromKiloBytes(MemoryMetrics.Total).Bytes)) <TL>memory used</TL>
</span>
}
</span>
<span class="fw-semibold fs-6">
@*IDK what to put here*@
</span>
</div>
</div>
</div>
<div class="col">
<div class="bg-gray-100 bg-opacity-70 rounded-2 px-6 py-5">
<div class="symbol symbol-30px me-5 mb-8">
<span class="symbol-label">
<i class="text-primary bx bx-lg bx-microchip"></i>
</span>
</div>
<div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (DiskMetrics == null)
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
<span>
@(Formatter.FormatSize(DiskMetrics.Used)) <TL>of</TL> @(Formatter.FormatSize(DiskMetrics.Total)) <TL>used</TL>
</span>
}
</span>
<span class="fw-semibold fs-6">
@*IDK what to put here*@
</span>
</div>
</div>
</div>
</div>
<div class="mt-3 row g-3 g-lg-6">
<div class="col">
<div class="bg-gray-100 bg-opacity-70 rounded-2 px-6 py-5">
<div class="symbol symbol-30px me-5 mb-8">
<span class="symbol-label">
<i class="text-primary bx bx-lg bx-purchase-tag"></i>
</span>
</div>
<div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (SystemStatus == null)
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
<span class="text-success">
<TL>Online</TL>
</span>
}
</span>
<span class="fw-semibold fs-6">
@if (SystemStatus == null)
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
<span>@(SystemStatus.Version)</span>
}
</span>
</div>
</div>
</div>
<div class="col">
<div class="bg-gray-100 bg-opacity-70 rounded-2 px-6 py-5">
<div class="symbol symbol-30px me-5 mb-8">
<span class="symbol-label">
<i class="text-primary bx bx-lg bx-fingerprint"></i>
</span>
</div>
<div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (SystemStatus == null)
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
<span>
@(SystemStatus.KernelVersion) - @(SystemStatus.Architecture)
</span>
}
</span>
<span class="fw-semibold fs-6">
@if (SystemStatus == null)
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
<TL>Host system information</TL>
}
</span>
</div>
</div>
</div>
<div class="col">
<div class="bg-gray-100 bg-opacity-70 rounded-2 px-6 py-5">
<div class="symbol symbol-30px me-5 mb-8">
<span class="symbol-label">
<i class="text-primary bx bx-lg bxl-docker"></i>
</span>
</div>
<div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (DockerMetrics == null)
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
<span>
<TL>@(DockerMetrics.Containers.Length)</TL>
</span>
}
</span>
<span class="fw-semibold fs-6">
@if (DockerMetrics == null)
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
<TL>Docker containers running</TL>
}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-5 card card-body">
<div class="d-flex justify-content-end">
<a href="/admin/nodes" class="btn btn-primary">
<TL>Cancel</TL>
</a>
</div>
</div>
</div>
</div>
}
</LazyLoader>
@code
{
[Parameter]
public int Id { get; set; }
private Node? Node;
private CpuMetrics CpuMetrics;
private MemoryMetrics MemoryMetrics;
private DiskMetrics DiskMetrics;
private DockerMetrics DockerMetrics;
private SystemStatus SystemStatus;
private async Task Load(LazyLoader arg)
{
Node = NodeRepository
.Get()
.FirstOrDefault(x => x.Id == Id);
if (Node != null)
{
Task.Run(async () =>
{
try
{
SystemStatus = await NodeService.GetStatus(Node);
await InvokeAsync(StateHasChanged);
CpuMetrics = await NodeService.GetCpuMetrics(Node);
await InvokeAsync(StateHasChanged);
MemoryMetrics = await NodeService.GetMemoryMetrics(Node);
await InvokeAsync(StateHasChanged);
DiskMetrics = await NodeService.GetDiskMetrics(Node);
await InvokeAsync(StateHasChanged);
DockerMetrics = await NodeService.GetDockerMetrics(Node);
await InvokeAsync(StateHasChanged);
}
catch (Exception e)
{
// ignored
}
});
}
}
}

View File

@@ -1,73 +0,0 @@
@page "/admin/notifications/debugging"
@using Moonlight.App.Services.Notifications
@using Moonlight.App.Models.Misc
@using Moonlight.App.Events
@using BlazorTable
@using Moonlight.App.Database.Entities.Notification
@using Moonlight.App.Services
@inject NotificationServerService NotificationServerService
@inject SmartTranslateService SmartTranslateService
@inject EventSystem Event
@implements IDisposable
@attribute [PermissionRequired(nameof(Permissions.AdminNotificationDebugging))]
<LazyLoader Load="Load">
<div class="card card-body">
<Table TableItem="ActiveNotificationClient" Items="Clients" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="ActiveNotificationClient" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Client.Id)" Sortable="false" Filterable="true"/>
<Column TableItem="ActiveNotificationClient" Title="@(SmartTranslateService.Translate("User"))" Field="@(x => x.Client.User.Email)" Sortable="false" Filterable="true"/>
<Column TableItem="ActiveNotificationClient" Title="" Field="@(x => x.Client.Id)" Sortable="false" Filterable="false">
<Template>
<WButton Text="@(SmartTranslateService.Translate("Send notification"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn-primary"
OnClick="() => SendSampleNotification(context)">
</WButton>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
@code
{
private ActiveNotificationClient[] Clients;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await Event.On<NotificationClient>("notifications.addClient", this, async client =>
{
Clients = await NotificationServerService.GetActiveClients();
await InvokeAsync(StateHasChanged);
});
await Event.On<NotificationClient>("notifications.removeClient", this, async client =>
{
Clients = await NotificationServerService.GetActiveClients();
await InvokeAsync(StateHasChanged);
});
}
}
private async Task Load(LazyLoader loader)
{
Clients = await NotificationServerService.GetActiveClients();
}
private async Task SendSampleNotification(ActiveNotificationClient client)
{
await client.SendAction(@"{""action"": ""notify"",""notification"":{""id"":999,""channel"":""Sample Channel"",""content"":""This is a sample Notification"",""title"":""Sample Notification"",""url"":""server/9b724fe2-d882-49c9-8c34-3414c7e4a17e""}}");
}
public async void Dispose()
{
await Event.Off("notifications.addClient", this);
await Event.Off("notifications.removeClient", this);
}
}

View File

@@ -1,148 +0,0 @@
@page "/admin/security/ddos"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@using BlazorTable
@using Moonlight.App.Helpers
@using Moonlight.App.Services.Background
@inject Repository<BlocklistIp> BlocklistIpRepository
@inject Repository<WhitelistIp> WhitelistIpRepository
@inject SmartTranslateService SmartTranslateService
@inject DdosProtectionService DdosProtectionService
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityDdos))]
<AdminSecurityNavigation Index="5"/>
<div class="card card-body mb-5">
<div class="d-flex justify-content-center fs-4">
<span class="me-3">
@(BlocklistIps.Length) <TL>blocked IPs</TL>
</span>
<span>
@(WhitelistIps.Length) <TL>whitelisted IPs</TL>
</span>
</div>
</div>
<div class="card card-body mb-5">
<div class="input-group input-group-lg">
<input @bind="Ip" type="text" class="form-control">
<WButton CssClasses="btn-secondary" OnClick="BlockIp" Text="@(SmartTranslateService.Translate("Block"))"/>
<WButton CssClasses="btn-secondary" OnClick="WhitelistIp" Text="@(SmartTranslateService.Translate("Whitelist"))"/>
</div>
</div>
<div class="card card-body mb-5">
<LazyLoader @ref="BlocklistLazyLoader" Load="LoadBlocklist">
<div class="table-responsive">
<Table TableItem="BlocklistIp" Items="BlocklistIps" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="BlocklistIp" Width="30%" Title="@(SmartTranslateService.Translate("Ip"))" Field="@(x => x.Ip)" Filterable="true" Sortable="false"/>
<Column TableItem="BlocklistIp" Width="30%" Title="@(SmartTranslateService.Translate("Packets"))" Field="@(x => x.Packets)" Filterable="true" Sortable="true">
<Template>
@(context.Packets) <TL>packets</TL>
</Template>
</Column>
<Column TableItem="BlocklistIp" Width="30%" Title="" Field="@(x => x.ExpiresAt)" Filterable="true" Sortable="true">
<Template>
@Formatter.FormatUptime(context.ExpiresAt - DateTime.UtcNow) <TL>remaining</TL>
</Template>
</Column>
<Column TableItem="BlocklistIp" Width="15%" Title="" Field="@(x => x.Id)" Filterable="false" Sortable="false">
<Template>
<div class="text-end">
<WButton Text="@(SmartTranslateService.Translate("Details"))" />
<DeleteButton Confirm="true" OnClick="() => RevokeBlocklistIp(context)" />
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
<div class="card card-body">
<LazyLoader @ref="WhitelistLazyLoader" Load="LoadWhitelist">
<div class="table-responsive">
<Table TableItem="WhitelistIp" Items="WhitelistIps" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="WhitelistIp" Width="85%" Title="@(SmartTranslateService.Translate("Ip"))" Field="@(x => x.Ip)" Filterable="true" Sortable="false"/>
<Column TableItem="WhitelistIp" Width="15%" Title="" Field="@(x => x.Id)" Filterable="false" Sortable="false">
<Template>
<div class="text-end">
<DeleteButton Confirm="true" OnClick="() => RevokeWhitelistIp(context)" />
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
@code
{
private BlocklistIp[] BlocklistIps = Array.Empty<BlocklistIp>();
private WhitelistIp[] WhitelistIps = Array.Empty<WhitelistIp>();
private LazyLoader BlocklistLazyLoader;
private LazyLoader WhitelistLazyLoader;
private string Ip = "";
private async Task LoadBlocklist(LazyLoader _)
{
BlocklistIps = BlocklistIpRepository
.Get()
.ToArray();
await InvokeAsync(StateHasChanged);
}
private async Task LoadWhitelist(LazyLoader _)
{
WhitelistIps = WhitelistIpRepository
.Get()
.ToArray();
await InvokeAsync(StateHasChanged);
}
private async Task BlockIp()
{
await DdosProtectionService.BlocklistIp(Ip, -1);
Ip = "";
await BlocklistLazyLoader.Reload();
}
private async Task RevokeBlocklistIp(BlocklistIp blocklistIp)
{
await DdosProtectionService.UnBlocklistIp(blocklistIp.Ip);
await BlocklistLazyLoader.Reload();
}
private async Task WhitelistIp()
{
WhitelistIpRepository.Add(new()
{
Ip = Ip
});
Ip = "";
await WhitelistLazyLoader.Reload();
}
private async Task RevokeWhitelistIp(WhitelistIp whitelistIp)
{
WhitelistIpRepository.Delete(whitelistIp);
await WhitelistLazyLoader.Reload();
}
}

View File

@@ -1,7 +0,0 @@
@page "/admin/security"
@using Moonlight.Shared.Components.Navigations
@attribute [PermissionRequired(nameof(Permissions.AdminSecurity))]
<AdminSecurityNavigation Index="0" />

View File

@@ -1,102 +0,0 @@
@page "/admin/security/ipbans"
@using Moonlight.Shared.Components.Navigations
@using BlazorTable
@using Moonlight.App.Database.Entities
@using Moonlight.App.Events
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject Repository<IpBan> IpBanRepository
@inject SmartTranslateService SmartTranslateService
@inject EventSystem Event
@inject ToastService ToastService
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityIpBans))]
<AdminSecurityNavigation Index="2"/>
<div class="card mb-5">
<div class="card-header">
<div class="card-title">
<TL>Ip Bans</TL>
</div>
<div class="card-toolbar">
<table class="w-100">
<tr>
<td class="w-100">
<input @bind="Ip" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter a ip"))"/>
</td>
<td>
<WButton OnClick="AddIpBan"
CssClasses="btn btn-primary ms-2"
Text="@(SmartTranslateService.Translate("Add"))"
WorkingText="@(SmartTranslateService.Translate("Adding"))">
</WButton>
</td>
</tr>
</table>
</div>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="table-responsive">
<Table TableItem="IpBan" Items="AllIpBans" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="IpBan" Title="@(SmartTranslateService.Translate("Ip"))" Field="@(x => x.Ip)" Filterable="true" Sortable="false"/>
<Column TableItem="IpBan" Title="" Field="@(x => x.Id)" Filterable="false" Sortable="false">
<Template>
<div class="text-end">
<DeleteButton Confirm="true" OnClick="() => DeleteIpBan(context)"></DeleteButton>
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
@code
{
private IpBan[] AllIpBans;
private string Ip;
private LazyLoader LazyLoader;
private Task Load(LazyLoader arg)
{
AllIpBans = IpBanRepository.Get().ToArray();
return Task.CompletedTask;
}
// Ip Bans
private async Task AddIpBan()
{
var ipBan = IpBanRepository.Add(new()
{
Ip = Ip
});
await LazyLoader.Reload();
Ip = "";
await InvokeAsync(StateHasChanged);
await Event.Emit("ipBan.update");
await ToastService.Success(
SmartTranslateService.Translate($"Successfully banned {ipBan.Ip}"));
}
private async Task DeleteIpBan(IpBan ban)
{
IpBanRepository.Delete(ban);
await Event.Emit("ipBan.update");
await LazyLoader.Reload();
}
}

View File

@@ -1,49 +0,0 @@
@page "/admin/security/logs"
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using BlazorTable
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services
@inject Repository<SecurityLog> SecurityLogRepository
@inject SmartTranslateService SmartTranslateService
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityLogs))]
<AdminSecurityNavigation Index="4"/>
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Security logs</TL>
</span>
</div>
<div class="card-body">
<LazyLoader Load="Load">
<div class="table-responsive">
<Table TableItem="SecurityLog" Items="SecurityLogs" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="SecurityLog" Width="80%" Title="@(SmartTranslateService.Translate("Text"))" Field="@(x => x.Text)" Filterable="true" Sortable="false"/>
<Column TableItem="SecurityLog" Width="20%" Title="@(SmartTranslateService.Translate("Timestamp"))" Field="@(x => x.CreatedAt)" Filterable="true" Sortable="true"/>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
@code
{
private SecurityLog[] SecurityLogs;
private Task Load(LazyLoader arg)
{
SecurityLogs = SecurityLogRepository
.Get()
.ToArray()
.OrderByDescending(x => x.CreatedAt)
.ToArray();
return Task.CompletedTask;
}
}

View File

@@ -1,219 +0,0 @@
@page "/admin/security/malware"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services.Background
@using Moonlight.App.Services
@using BlazorTable
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.ApiClients.Wings
@using Moonlight.App.Database.Entities
@using Moonlight.App.Events
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Misc
@using Moonlight.App.Repositories
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions
@inject MalwareBackgroundScanService MalwareBackgroundScanService
@inject SmartTranslateService SmartTranslateService
@inject ServerService ServerService
@inject ToastService ToastService
@inject SessionServerService SessionServerService
@inject Repository<Server> ServerRepository
@inject Repository<User> UserRepository
@inject EventSystem Event
@implements IDisposable
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityMalware))]
<AdminSecurityNavigation Index="1"/>
<div class="row">
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-body">
@if (MalwareBackgroundScanService.IsRunning)
{
<span class="fs-3 spinner-border align-middle me-3"></span>
}
<span class="fs-3">@(MalwareBackgroundScanService.Status)</span>
</div>
<div class="card-footer">
@if (MalwareBackgroundScanService.IsRunning)
{
<button class="btn btn-success disabled">
<TL>Scan in progress</TL>
</button>
}
else
{
<div class="mb-5">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="scanAllServers" @bind="MalwareBackgroundScanService.ScanAllServers">
<label class="form-check-label" for="scanAllServers">
<TL>Scan all servers</TL>
</label>
</div>
</div>
<WButton Text="@(SmartTranslateService.Translate("Start scan"))"
CssClasses="btn-success me-3"
OnClick="Scan">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Purge page"))"
CssClasses="btn-danger"
OnClick="PurgeSelected">
</WButton>
}
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Results</TL>
</span>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoaderResults" Load="LoadResults">
<div class="table-responsive">
<Table @ref="Table" TableItem="Server" Items="ScanResults.Keys" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Server"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<a href="/server/@(context.Uuid)">@(context.Name)</a>
</Template>
</Column>
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Title"))" Field="@(x => ScanResults[x].Title)" Sortable="false" Filterable="true" />
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Description"))" Field="@(x => ScanResults[x].Description)" Sortable="false" Filterable="true" />
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
</div>
</div>
@code
{
private readonly Dictionary<Server, MalwareScanResult> ScanResults = new();
private Table<Server> Table;
private LazyLoader LazyLoaderResults;
protected override async Task OnInitializedAsync()
{
await Event.On<Object>("malwareScan.status", this, async o => { await InvokeAsync(StateHasChanged); });
await Event.On<Server?>("malwareScan.result", this, async server =>
{
lock (MalwareBackgroundScanService.ScanResults)
{
if (server == null)
return;
ScanResults.Add(server, MalwareBackgroundScanService.ScanResults[server]);
}
await InvokeAsync(StateHasChanged);
});
}
private Task LoadResults(LazyLoader arg)
{
ScanResults.Clear();
lock (MalwareBackgroundScanService.ScanResults)
{
foreach (var result in MalwareBackgroundScanService.ScanResults)
{
ScanResults.Add(result.Key, result.Value);
}
}
return Task.CompletedTask;
}
public async void Dispose()
{
await Event.Off("malwareScan.status", this);
await Event.Off("malwareScan.result", this);
}
private async Task PurgeSelected()
{
int users = 0;
int servers = 0;
int allServersCount = Table.FilteredItems.Count();
int position = 0;
await ToastService.CreateProcessToast("purgeProcess", "Purging");
foreach (var item in Table.FilteredItems)
{
position++;
if (item == null)
continue;
try
{
var server = ServerRepository.Get()
.Include(x => x.Owner)
.FirstOrDefault(x => x.Id == item.Id);
if(server == null)
continue;
await ToastService.UpdateProcessToast("purgeProcess", $"[{position}/{allServersCount}] {server.Name}");
ScanResults.Remove(item);
await InvokeAsync(StateHasChanged);
// Owner
server.Owner.Status = UserStatus.Banned;
UserRepository.Update(server.Owner);
users++;
try
{
await SessionServerService.ReloadUserSessions(server.Owner);
}
catch (Exception) {/* Ignored */}
// Server itself
await ServerService.SetPowerState(server, PowerSignal.Kill);
await ServerService.Delete(server);
servers++;
}
catch (Exception e)
{
Logger.Warn($"Error purging server: {item.Uuid}");
Logger.Warn(e);
await ToastService.Error(
$"Failed to purge server '{item.Name}': {e.Message}"
);
}
}
await ToastService.RemoveProcessToast("purgeProcess");
await ToastService.Success($"Successfully purged {servers} servers by {users} users");
}
private async Task Scan()
{
ScanResults.Clear();
await InvokeAsync(StateHasChanged);
await MalwareBackgroundScanService.Start();
}
}

View File

@@ -1,132 +0,0 @@
@page "/admin/security/permissiongroups"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using BlazorTable
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions
@inject SmartTranslateService SmartTranslateService
@inject Repository<PermissionGroup> PermissionGroupRepository
@inject SessionServerService SessionServerService
@inject Repository<User> UserRepository
@inject AlertService AlertService
@inject ToastService ToastService
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityPermissionGroups))]
<AdminSecurityNavigation Index="3"/>
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Permission groups</TL>
</span>
<div class="card-toolbar">
<WButton Text="@(SmartTranslateService.Translate("New"))"
CssClasses="btn-sm btn-success"
OnClick="NewGroupPermission">
</WButton>
</div>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="table-responsive">
<Table TableItem="PermissionGroup" Items="AllPermissionGroups" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="PermissionGroup" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Filterable="true" Sortable="false"/>
<Column TableItem="PermissionGroup" Title="" Field="@(x => x.Id)" Filterable="false" Sortable="false">
<Template>
<div class="text-end">
<WButton Text="@(SmartTranslateService.Translate("Edit permissions"))"
CssClasses="btn-primary me-2"
OnClick="() => EditPermissions(context)">
</WButton>
<DeleteButton Confirm="true" OnClick="() => DeletePermissionGroup(context)"></DeleteButton>
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
<PermissionEditor @ref="PermissionEditor" OnSave="OnSave"/>
@code
{
private PermissionGroup[] AllPermissionGroups;
private PermissionGroup CurrentPermissionGroup;
private LazyLoader LazyLoader;
private PermissionEditor PermissionEditor;
private Task Load(LazyLoader arg)
{
AllPermissionGroups = PermissionGroupRepository
.Get()
.ToArray();
return Task.CompletedTask;
}
private async Task EditPermissions(PermissionGroup group)
{
CurrentPermissionGroup = group;
PermissionEditor.InitialData = CurrentPermissionGroup.Permissions;
await PermissionEditor.Launch();
}
private async Task DeletePermissionGroup(PermissionGroup group)
{
PermissionGroupRepository.Delete(group);
await LazyLoader.Reload();
}
private async Task OnSave(byte[] data)
{
CurrentPermissionGroup.Permissions = data;
PermissionGroupRepository.Update(CurrentPermissionGroup);
await ToastService.Success("Successfully modified permissions");
var usersWithTheGroup = UserRepository
.Get()
.Include(x => x.PermissionGroup)
.Where(x => x.PermissionGroup != null)
.Where(x => x.PermissionGroup!.Id == CurrentPermissionGroup.Id)
.ToArray();
foreach (var user in usersWithTheGroup)
{
await SessionServerService.ReloadUserSessions(user);
}
}
private async Task NewGroupPermission()
{
var name = await AlertService.Text(
SmartTranslateService.Translate("Enter the name for the new group"),
"",
""
);
if(string.IsNullOrEmpty(name))
return;
var group = new PermissionGroup()
{
Name = name,
Permissions = Array.Empty<byte>()
};
PermissionGroupRepository.Add(group);
await LazyLoader.Reload();
}
}

View File

@@ -1,98 +0,0 @@
@page "/admin/servers/cleanup"
@using Moonlight.App.Events
@using Moonlight.App.Services.Background
@using Moonlight.Shared.Components.Navigations
@inject CleanupService CleanupService
@inject EventSystem Event
@implements IDisposable
@attribute [PermissionRequired(nameof(Permissions.AdminServerCleanup))]
<AdminServersNavigation Index="2" />
<div class="row g-5 g-xl-10 mb-5 mb-xl-10">
<div class="col-xl-3">
<div class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-end h-xl-100" style="background-color: #170049;">
<div class="card-header pt-5 mb-3">
<div class="d-flex flex-center rounded-circle h-80px w-80px" style="border: 1px rgba(255, 255, 255, 0.4);background-color: #7239EA">
<i class="text-white bx bxs-skull bx-lg"></i>
</div>
</div>
<div class="card-body d-flex align-items-end mb-3">
<div class="d-flex align-items-center">
<span class="fs-4hx text-white fw-bold me-6">@(CleanupService.ServersCleaned)</span>
<div class="fw-bold fs-6 text-white">
<span class="d-block">
<TL>Servers</TL>
</span>
<span>
<TL>stopped</TL>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3">
<div class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-end h-xl-100" style="background-color: #170049;">
<div class="card-header pt-5 mb-3">
<div class="d-flex flex-center rounded-circle h-80px w-80px" style="border: 1px rgba(255, 255, 255, 0.4);background-color: #7239EA">
<i class="text-white bx bx-transfer bx-lg"></i>
</div>
</div>
<div class="card-body d-flex align-items-end mb-3">
<div class="d-flex align-items-center">
<span class="fs-4hx text-white fw-bold me-6">@(CleanupService.CleanupsPerformed)</span>
<div class="fw-bold fs-6 text-white">
<span class="d-block">
<TL>Cleanups</TL>
</span>
<span>
<TL>executed</TL>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3">
<div class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-end h-xl-100" style="background-color: #170049;">
<div class="card-header pt-5 mb-3">
<div class="d-flex flex-center rounded-circle h-80px w-80px" style="border: 1px rgba(255, 255, 255, 0.4);background-color: #7239EA">
<i class="text-white bx bx-rocket bx-lg"></i>
</div>
</div>
<div class="card-body d-flex align-items-end mb-3">
<div class="d-flex align-items-center">
<span class="fs-4hx text-white fw-bold me-6">@(CleanupService.ServersRunning)</span>
<div class="fw-bold fs-6 text-white">
<span class="d-block">
<TL>Running cleanup</TL>
</span>
<span>
<TL>servers</TL>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
@code
{
protected override async Task OnInitializedAsync()
{
await Event.On<Object>("cleanup.updated", this, async _ =>
{
await InvokeAsync(StateHasChanged);
});
}
public async void Dispose()
{
await Event.Off("cleanup.updated", this);
}
}

View File

@@ -1,230 +0,0 @@
@page "/admin/servers/editx/{id:int}"
@using Moonlight.App.Services
@using Moonlight.App.Repositories.Servers
@using Moonlight.App.Database.Entities
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Models.Forms
@using Moonlight.App.Repositories
@using Mappy.Net
@inject SmartTranslateService SmartTranslateService
@inject ServerRepository ServerRepository
@inject ImageRepository ImageRepository
@inject Repository<User> UserRepository
@attribute [PermissionRequired(nameof(Permissions.AdminServerEdit))]
<LazyLoader @ref="LazyLoader" Load="Load">
@if (Server == null)
{
<div class="alert alert-danger">
<TL>No server with this id found</TL>
</div>
}
else
{
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
<div class="row mb-5">
<div class="card card-body p-10">
<label class="form-label">
<TL>Identifier</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-id-card"></i>
</span>
<input type="number" class="form-control disabled" disabled="" value="@(Server.Id)">
</div>
<label class="form-label">
<TL>UuidIdentifier</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-id-card"></i>
</span>
<input type="text" class="form-control disabled" disabled="" value="@(Server.Uuid)">
</div>
<label class="form-label">
<TL>Server name</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-purchase-tag-alt"></i>
</span>
<InputText @bind-Value="Model.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Server name"))"></InputText>
</div>
<label class="form-label">
<TL>Owner</TL>
</label>
<div class="input-group mb-5">
<SmartDropdown T="User"
@bind-Value="Model.Owner"
Items="Users"
DisplayFunc="@(x => x.Email)"
SearchProp="@(x => x.Email)">
</SmartDropdown>
</div>
</div>
</div>
<div class="row mb-5">
<div class="card card-body p-10">
<label class="form-label">
<TL>Cpu cores</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-chip"></i>
</span>
<InputNumber @bind-Value="Model.Cpu" type="number" class="form-control"></InputNumber>
<span class="input-group-text">
<TL>CPU Cores (100% = 1 Core)</TL>
</span>
</div>
<label class="form-label">
<TL>Memory</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-microchip"></i>
</span>
<InputNumber @bind-Value="Model.Memory" type="number" class="form-control"></InputNumber>
<span class="input-group-text">
MB
</span>
</div>
<label class="form-label">
<TL>Disk</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-hdd"></i>
</span>
<InputNumber @bind-Value="Model.Disk" type="number" class="form-control"></InputNumber>
<span class="input-group-text">
MB
</span>
</div>
</div>
</div>
<div class="row mb-5">
<div class="card card-body p-10">
<label class="form-label">
<TL>Override startup command</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-terminal"></i>
</span>
<InputText @bind-Value="Model.OverrideStartup" type="text" class="form-control" placeholder="@(Server.Image.Startup)"></InputText>
</div>
<label class="form-label">
<TL>Docker image</TL>
</label>
<select @bind="Model.DockerImageIndex" class="form-select">
@foreach (var image in DockerImages)
{
<option value="@(DockerImages.IndexOf(image))">@(image.Name)</option>
}
</select>
<label class="form-label">
<TL>Cleanup exception</TL>
</label>
<input @bind="Model.IsCleanupException" class="form-check" type="checkbox"/>
</div>
</div>
<div class="row mb-5">
@foreach (var vars in Server.Variables.Chunk(4))
{
<div class="row mb-3">
@foreach (var variable in vars)
{
<div class="col">
<div class="card card-body">
<label class="form-label">
<TL>Name</TL>
</label>
<div class="input-group mb-5">
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
</div>
<label class="form-label">
<TL>Value</TL>
</label>
<div class="input-group mb-5">
<input @bind="variable.Value" type="text" class="form-control">
</div>
</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>
<button class="btn btn-success" type="submit"><TL>Save</TL></button>
</div>
</div>
</div>
</SmartForm>
}
</LazyLoader>
@code
{
[Parameter]
public int Id { get; set; }
private LazyLoader LazyLoader;
private Server? Server;
private List<DockerImage> DockerImages;
private List<Image> Images;
private User[] Users;
private ServerEditDataModel Model = new();
private async Task Load(LazyLoader lazyLoader)
{
Server = ServerRepository
.Get()
.Include(x => x.Variables)
.FirstOrDefault(x => x.Id == Id);
if (Server != null)
{
await lazyLoader.SetText("Loading images");
Images = ImageRepository
.Get()
.Include(x => x.Variables)
.Include(x => x.DockerImages)
.ToList();
await lazyLoader.SetText("Loading docker images");
DockerImages = Images
.First(x => x.Id == Server.Image.Id).DockerImages
.ToList();
await lazyLoader.SetText("Loading users");
Users = UserRepository.Get().ToArray();
Model = Mapper.Map<ServerEditDataModel>(Server);
}
}
private async Task OnValidSubmit()
{
// Overwrite data using mapper
Server = Mapper.Map(Server, Model);
ServerRepository.Update(Server!);
await LazyLoader.Reload();
}
}

View File

@@ -1,378 +0,0 @@
@page "/admin/servers/images/edit/{Id:int}"
@using Moonlight.App.Repositories
@using Moonlight.Shared.Components.FileManagerPartials
@using Moonlight.App.Database.Entities
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Newtonsoft.Json
@inject ImageRepository ImageRepository
@inject SmartTranslateService SmartTranslateService
@inject ToastService ToastService
@inject FileDownloadService FileDownloadService
@attribute [PermissionRequired(nameof(Permissions.AdminServerImageEdit))]
<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>
@code
{
[Parameter]
public int Id { get; set; }
private Image? Image;
private List<string> Tags;
private string AddTagName = "";
private DockerImage NewDockerImage = new();
private ImageVariable ImageVariable = new();
private FileEditor Editor;
private int DefaultImageIndex
{
get
{
var i = Image.DockerImages.FirstOrDefault(x => x.Default);
return i?.Id ?? -1;
}
set
{
foreach (var image in Image!.DockerImages)
{
image.Default = false;
}
var i = Image.DockerImages.FirstOrDefault(x => x.Id == value);
if (i != null)
i.Default = true;
}
}
private LazyLoader LazyLoader;
private Task Load(LazyLoader arg)
{
Image = ImageRepository
.Get()
.Include(x => x.Variables)
.Include(x => x.DockerImages)
.FirstOrDefault(x => x.Id == Id);
if (Image != null)
{
Tags = new();
foreach (var tag in JsonConvert.DeserializeObject<string[]>(Image.TagsJson) ?? Array.Empty<string>())
{
Tags.Add(tag);
}
// Editor
}
return Task.CompletedTask;
}
private void AddTag()
{
Tags.Add(AddTagName);
}
private void RemoveTag(string tag)
{
Tags.Remove(tag);
}
private void AddDockerImage()
{
Image!.DockerImages.Add(NewDockerImage);
NewDockerImage = new();
}
private void RemoveDockerImage(DockerImage image)
{
Image!.DockerImages.Remove(image);
}
private void AddVariable()
{
Image!.Variables.Add(ImageVariable);
ImageVariable = new();
}
private void RemoveVariable(ImageVariable variable)
{
Image!.Variables.Remove(variable);
}
private async Task Save()
{
if (Image == null)
return;
Image.TagsJson = JsonConvert.SerializeObject(Tags);
Image.InstallScript = await Editor.GetData();
ImageRepository.Update(Image);
await ToastService.Success(SmartTranslateService.Translate("Successfully saved image"));
await LazyLoader.Reload();
}
private async Task Export()
{
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

@@ -1,228 +0,0 @@
@page "/admin/servers/images"
@using BlazorTable
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using System.Text
@using Moonlight.App.Helpers
@using Newtonsoft.Json
@using Moonlight.Shared.Components.Navigations
@inject Repository<Image> ImageRepository
@inject Repository<ImageVariable> ImageVariableRepository
@inject Repository<Server> ServerRepository
@inject SmartTranslateService SmartTranslateService
@inject AlertService AlertService
@attribute [PermissionRequired(nameof(Permissions.AdminServerImages))]
<AdminServersNavigation Index="4" />
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
<span class="card-label fw-bold fs-3 mb-1">
<TL>Images</TL>
</span>
</h3>
<div class="card-toolbar">
<a href="/admin/servers/images/new" class="btn btn-sm btn-light-success me-3">
<i class="bx bx-layer-plus"></i>
<TL>New image</TL>
</a>
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
<label for="fileUpload" class="btn btn-sm btn-light-primary me-3">
<span class="svg-icon svg-icon-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
</svg>
</span>
<TL>Import</TL>
</label>
<InputFile OnChange="OnEggFileChanged" type="file" id="eggFileUpload" hidden="" multiple=""/>
<label for="eggFileUpload" class="btn btn-sm btn-light-primary">
<span class="svg-icon svg-icon-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
</svg>
</span>
<TL>Import pterodactyl egg</TL>
</label>
</div>
</div>
<div class="card-body pt-0">
@if (Images.Any())
{
<div class="table-responsive">
<Table TableItem="Image" Items="Images" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Description"))" Field="@(x => x.Description)" Sortable="true" Filterable="true"/>
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Uuid"))" Field="@(x => x.Uuid)" Sortable="true" Filterable="true"/>
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Servers with this image"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
@{
var i = ServersCount.TryGetValue(context, out var value) ? value.ToString() : "N/A";
}
<span>
@(i)
</span>
</Template>
</Column>
<Column TableItem="Image" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<a href="/admin/servers/images/edit/@(context.Id)">
@(SmartTranslateService.Translate("Edit"))
</a>
</Template>
</Column>
<Column TableItem="Image" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
CssClasses="btn-danger"
OnClick="() => Delete(context)">
</WButton>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
}
else
{
<div class="alert alert-info">
<TL>No images found</TL>
</div>
}
</div>
</div>
</LazyLoader>
@code
{
private Image[] Images;
private LazyLoader LazyLoader;
private readonly Dictionary<Image, int> ServersCount = new();
private async Task Load(LazyLoader lazyLoader)
{
await lazyLoader.SetText("Loading images");
Images = ImageRepository
.Get()
.Include(x => x.DockerImages)
.Include(x => x.Variables)
.ToArray();
await lazyLoader.SetText("Counting image servers");
ServersCount.Clear();
foreach (var image in Images)
{
var c = ServerRepository
.Get()
.Include(x => x.Image)
.Count(x => x.Image.Id == image.Id);
ServersCount.Add(image, c);
}
}
private async Task Delete(Image image)
{
var variables = image.Variables.ToArray();
image.Variables.Clear();
ImageRepository.Update(image);
foreach (var v in variables)
{
ImageVariableRepository.Delete(v);
}
ImageRepository.Delete(image);
await LazyLoader.Reload();
}
private async Task OnFileChanged(InputFileChangeEventArgs arg)
{
foreach (var browserFile in arg.GetMultipleFiles())
{
try
{
var stream = browserFile.OpenReadStream(1024 * 1024 * 100);
var data = new byte[browserFile.Size];
_ = await stream.ReadAsync(data, 0, data.Length);
var text = Encoding.UTF8.GetString(data);
var image = JsonConvert.DeserializeObject<Image>(text)!;
image.Uuid = Guid.NewGuid();
ImageRepository.Add(image);
await AlertService.Success(SmartTranslateService.Translate("Successfully imported image"));
await LazyLoader.Reload();
}
catch (Exception e)
{
await AlertService.Error(SmartTranslateService.Translate("An unknown error occured while uploading and importing the image"));
Logger.Error("Error importing image");
Logger.Error(e);
}
}
await InvokeAsync(StateHasChanged);
}
private async Task OnEggFileChanged(InputFileChangeEventArgs arg)
{
var b = await AlertService.YesNo(
SmartTranslateService.Translate("Attention"),
SmartTranslateService.Translate("Imported pterodactyl eggs can result in broken images. We do not support pterodactyl eggs"),
SmartTranslateService.Translate("I take the risk"),
SmartTranslateService.Translate("Cancel")
);
if(!b)
return;
foreach (var browserFile in arg.GetMultipleFiles())
{
try
{
var stream = browserFile.OpenReadStream(1024 * 1024 * 100);
var data = new byte[browserFile.Size];
_ = await stream.ReadAsync(data, 0, data.Length);
var json = Encoding.UTF8.GetString(data);
var image = EggConverter.Convert(json);
ImageRepository.Add(image);
await AlertService.Success(SmartTranslateService.Translate("Successfully imported image"));
await LazyLoader.Reload();
}
catch (Exception e)
{
await AlertService.Error(SmartTranslateService.Translate("An unknown error occured while uploading and importing the image"));
Logger.Error("Error importing image");
Logger.Error(e);
}
}
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,340 +0,0 @@
@page "/admin/servers/images/new"
@using Moonlight.App.Repositories
@using Moonlight.Shared.Components.FileManagerPartials
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Newtonsoft.Json
@inject ImageRepository ImageRepository
@inject SmartTranslateService SmartTranslateService
@inject NavigationManager NavigationManager
@inject ToastService ToastService
<OnlyAdmin>
<div class="row">
<LazyLoader @ref="LazyLoader" Load="Load">
<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("Save"))"
WorkingText="@(SmartTranslateService.Translate("Saving"))"
CssClasses="btn-success"
OnClick="Save">
</WButton>
</div>
</div>
</div>
</LazyLoader>
</div>
</OnlyAdmin>
@code
{
private Image Image = new()
{
Variables = new(),
DockerImages = new(),
InstallScript = "# install script here",
Name = "Name",
TagsJson = "[]",
Description = "Description",
Startup = "",
Uuid = Guid.NewGuid(),
ConfigFiles = "{}",
InstallEntrypoint = "ash",
StartupDetection = "Done",
StopCommand = "^C",
InstallDockerImage = "ghcr.io/pterodactyl/installers:alpine"
};
private List<string> Tags;
private string AddTagName = "";
private DockerImage NewDockerImage = new();
private ImageVariable ImageVariable = new();
private FileEditor Editor;
private int DefaultImageIndex
{
get
{
var i = Image.DockerImages.FirstOrDefault(x => x.Default);
return i?.Id ?? -1;
}
set
{
foreach (var image in Image!.DockerImages)
{
image.Default = false;
}
var i = Image.DockerImages.FirstOrDefault(x => x.Id == value);
if (i != null)
i.Default = true;
}
}
private LazyLoader LazyLoader;
private Task Load(LazyLoader arg)
{
Tags = new();
return Task.CompletedTask;
}
private void AddTag()
{
Tags.Add(AddTagName);
}
private void RemoveTag(string tag)
{
Tags.Remove(tag);
}
private void AddDockerImage()
{
Image!.DockerImages.Add(NewDockerImage);
NewDockerImage = new();
}
private void RemoveDockerImage(DockerImage image)
{
Image!.DockerImages.Remove(image);
}
private void AddVariable()
{
Image!.Variables.Add(ImageVariable);
ImageVariable = new();
}
private void RemoveVariable(ImageVariable variable)
{
Image!.Variables.Remove(variable);
}
private async Task Save()
{
if (Image == null)
return;
Image.TagsJson = JsonConvert.SerializeObject(Tags);
Image.InstallScript = await Editor.GetData();
ImageRepository.Add(Image);
await ToastService.Success(SmartTranslateService.Translate("Successfully added image"));
NavigationManager.NavigateTo("/admin/servers/images");
}
}

View File

@@ -1,86 +0,0 @@
@page "/admin/servers"
@using Moonlight.App.Services
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories.Servers
@using BlazorTable
@using Microsoft.EntityFrameworkCore
@using Moonlight.Shared.Components.Navigations
@inject ServerRepository ServerRepository
@inject SmartTranslateService SmartTranslateService
@attribute [PermissionRequired(nameof(Permissions.AdminServers))]
<AdminServersNavigation Index="0" />
<LazyLoader Load="Load">
<div class="card">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
<span class="card-label fw-bold fs-3 mb-1">
<TL>Servers</TL>
</span>
</h3>
<div class="card-toolbar">
<a href="/admin/servers/new" class="btn btn-sm btn-light-success">
<i class="bx bx-layer-plus"></i>
<TL>New server</TL>
</a>
</div>
</div>
<div class="card-body pt-0">
@if (Servers.Any())
{
<div class="table-responsive">
<Table TableItem="Server" Items="Servers" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
<Template>
<a href="/server/@(context.Uuid)">@(context.Name)</a>
</Template>
</Column>
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Cores"))" Field="@(x => x.Cpu)" Sortable="true" Filterable="true"/>
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Memory"))" Field="@(x => x.Memory)" Sortable="true" Filterable="true"/>
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Disk"))" Field="@(x => x.Disk)" Sortable="true" Filterable="true"/>
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true">
<Template>
<a href="/admin/users/view/@(context.Owner.Id)/">@context.Owner.Email</a>
</Template>
</Column>
<Column TableItem="Server" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<a href="/admin/servers/view/@(context.Id)">
@(SmartTranslateService.Translate("Manage"))
</a>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
}
else
{
<div class="alert alert-info">
<TL>No servers found</TL>
</div>
}
</div>
</div>
</LazyLoader>
@code
{
private Server[] Servers;
private Task Load(LazyLoader lazyLoader)
{
Servers = ServerRepository
.Get()
.Include(x => x.Owner)
.ToArray() // Execute query and use the moonlight instance to sort
.OrderBy(x => x.Id)
.ToArray();
return Task.CompletedTask;
}
}

View File

@@ -1,208 +0,0 @@
@page "/admin/servers/manager"
@using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Servers
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Moonlight.App.Database.Entities
@using BlazorTable
@using Microsoft.EntityFrameworkCore
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.ApiClients.Wings
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Misc
@inject NodeRepository NodeRepository
@inject NodeService NodeService
@inject ServerRepository ServerRepository
@inject SmartTranslateService SmartTranslateService
@inject AlertService AlertService
@inject ServerService ServerService
@attribute [PermissionRequired(nameof(Permissions.AdminServerManager))]
<AdminServersNavigation Index="1"/>
<div class="card">
<div class="card-header">
<span class="card-title">
@if (IsRunning)
{
<span><TL>Status</TL>:&nbsp;<TL>Currently scanning</TL> @(Node?.Name)</span>
}
else
{
<span><TL>Status</TL>:&nbsp;<TL>Scan complete</TL></span>
}
</span>
<div class="card-toolbar">
<WButton Text="@(SmartTranslateService.Translate("Refresh"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn-primary me-2"
OnClick="() => Task.Run(Scan)">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Stop all"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn-danger me-2"
OnClick="StopAll">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Kill all"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn-danger"
OnClick="KillAll">
</WButton>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<Table TableItem="RunningServer" Items="RunningServers" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="RunningServer" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Server.Name)" Sortable="true" Filterable="true">
<Template>
<a href="/server/@(context.Server.Uuid)">@(context.Server.Name)</a>
</Template>
</Column>
<Column TableItem="RunningServer" Title="@(SmartTranslateService.Translate("Cpu usage"))" Field="@(x => x.Container.Cpu)" Sortable="true" Filterable="true">
<Template>
<span>@(context.Container.Cpu)%</span>
</Template>
</Column>
<Column TableItem="RunningServer" Title="@(SmartTranslateService.Translate("Memory usage"))" Field="@(x => x.Container.Memory)" Sortable="true" Filterable="true">
<Template>
<span>@(Formatter.FormatSize(context.Container.Memory))</span>
</Template>
</Column>
<Column TableItem="RunningServer" Title="@(SmartTranslateService.Translate("Network in"))" Field="@(x => x.Container.NetworkIn)" Sortable="true" Filterable="true">
<Template>
<span>@(Formatter.FormatSize(context.Container.NetworkIn))</span>
</Template>
</Column>
<Column TableItem="RunningServer" Title="@(SmartTranslateService.Translate("Network out"))" Field="@(x => x.Container.NetworkOut)" Sortable="true" Filterable="true">
<Template>
<span>@(Formatter.FormatSize(context.Container.NetworkOut))</span>
</Template>
</Column>
<Column TableItem="RunningServer" Title="@(SmartTranslateService.Translate("Image"))" Field="@(x => x.Server.Image.Name)" Sortable="true" Filterable="true">
<Template>
<a href="/admin/servers/images/edit/@(context.Server.Image.Id)">@(context.Server.Image.Name)</a>
</Template>
</Column>
<Column TableItem="RunningServer" Title="@(SmartTranslateService.Translate("Node"))" Field="@(x => x.Server.Node.Name)" Sortable="true" Filterable="true">
<Template>
<a href="/admin/nodes/view/@(context.Server.Node.Id)">@(context.Server.Node.Name)</a>
</Template>
</Column>
<Column TableItem="RunningServer" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Server.Owner.Email)" Sortable="true" Filterable="true">
<Template>
<a href="/admin/users/view/@(context.Server.Owner.Id)/">@context.Server.Owner.Email</a>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</div>
</div>
@code
{
private List<RunningServer> RunningServers = new();
private bool IsRunning;
private Node? Node;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await Task.Run(Scan);
}
}
private async Task Scan()
{
IsRunning = true;
RunningServers.Clear();
await InvokeAsync(StateHasChanged);
var nodes = NodeRepository.Get().ToArray();
Node = nodes.First();
foreach (var node in nodes)
{
Node = node;
await InvokeAsync(StateHasChanged);
try
{
var dockerMetrics = await NodeService.GetDockerMetrics(node);
foreach (var container in dockerMetrics.Containers)
{
if (Guid.TryParse(container.Name, out Guid uuid))
{
var server = ServerRepository
.Get()
.Include(x => x.Owner)
.Include(x => x.Node)
.Include(x => x.Image)
.FirstOrDefault(x => x.Uuid == uuid);
if (server != null)
{
RunningServers.Add(new()
{
Server = server,
Container = container
});
await InvokeAsync(StateHasChanged);
}
}
}
}
catch (Exception)
{
// ignored
}
}
IsRunning = false;
await InvokeAsync(StateHasChanged);
}
private async Task StopAll()
{
var b = await AlertService.YesNo(
SmartTranslateService.Translate("Stop all servers"),
SmartTranslateService.Translate("Do you really want to stop all running servers?"),
SmartTranslateService.Translate("Yes"),
SmartTranslateService.Translate("No")
);
if (b)
{
foreach (var runningServer in RunningServers)
{
await ServerService.SetPowerState(runningServer.Server, PowerSignal.Stop);
}
}
}
private async Task KillAll()
{
var b = await AlertService.YesNo(
SmartTranslateService.Translate("Kill all servers"),
SmartTranslateService.Translate("Do you really want to kill all running servers?"),
SmartTranslateService.Translate("Yes"),
SmartTranslateService.Translate("No")
);
if (b)
{
foreach (var runningServer in RunningServers)
{
await ServerService.SetPowerState(runningServer.Server, PowerSignal.Kill);
}
}
}
}

View File

@@ -1,271 +0,0 @@
@page "/admin/servers/new"
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Exceptions
@using Moonlight.App.Helpers
@using Moonlight.App.Services.Interop
@using Moonlight.App.Models.Forms
@inject NodeRepository NodeRepository
@inject ImageRepository ImageRepository
@inject ServerService ServerService
@inject SmartTranslateService SmartTranslateService
@inject AlertService AlertService
@inject ToastService ToastService
@inject NavigationManager NavigationManager
@inject UserRepository UserRepository
@attribute [PermissionRequired(nameof(Permissions.AdminNewServer))]
<LazyLoader Load="Load">
<SmartForm Model="Model" OnValidSubmit="Create">
<div class="row mb-5">
<div class="card card-body p-10">
<label class="form-label">
<TL>Server name</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-purchase-tag-alt"></i>
</span>
<InputText @bind-Value="Model.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Server name"))"></InputText>
</div>
<label class="form-label">
<TL>Owner</TL>
</label>
<div class="input-group mb-5">
<SmartDropdown
@bind-Value="Model.Owner"
Items="Users"
DisplayFunc="@(x => x.Email)"
SearchProp="@(x => x.Email)">
</SmartDropdown>
</div>
</div>
</div>
<div class="row mb-5">
<div class="card card-body p-10">
<label class="form-label">
<TL>Cpu cores</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-chip"></i>
</span>
<InputNumber @bind-Value="Model.Cpu" class="form-control"></InputNumber>
<span class="input-group-text">
<TL>CPU Cores (100% = 1 Core)</TL>
</span>
</div>
<label class="form-label">
<TL>Memory</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-microchip"></i>
</span>
<InputNumber @bind-Value="Model.Memory" class="form-control"></InputNumber>
<span class="input-group-text">
MB
</span>
</div>
<label class="form-label">
<TL>Disk</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-hdd"></i>
</span>
<InputNumber @bind-Value="Model.Disk" class="form-control"></InputNumber>
<span class="input-group-text">
MB
</span>
</div>
<label class="form-label">
<TL>Node</TL>
</label>
<div class="input-group mb-5">
<SmartSelect @bind-Value="Model.Node"
Items="Nodes"
DisplayField="@(x => x.Name)">
</SmartSelect>
</div>
</div>
</div>
<div class="row mb-5">
<div class="card card-body p-10">
<label class="form-label">
<TL>Image</TL>
</label>
<div class="mb-5">
<SmartSelect TField="Image" @bind-Value="Model.Image" Items="Images" DisplayField="@(x => x.Name)" OnChange="OnChange"></SmartSelect>
</div>
@if (Model.Image != null)
{
<label class="form-label">
<TL>Override startup</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-terminal"></i>
</span>
<InputText @bind-Value="Model.OverrideStartup" type="text" class="form-control" placeholder="@(Model.Image.Startup)"></InputText>
</div>
<label class="form-label">
<TL>Docker image</TL>
</label>
<InputSelect TValue="int" @bind-Value="Model.DockerImageIndex" class="form-control">
@foreach (var image in Model.Image.DockerImages)
{
<option value="@(Model.Image.DockerImages.IndexOf(image))">@(image.Name)</option>
}
</InputSelect>
}
</div>
</div>
<div class="row mb-5">
<div class="card card-body">
@if (Model.Image != null)
{
<div class="mt-9 row d-flex">
@foreach (var vars in ServerVariables.Chunk(3))
{
<div class="row row-cols-3 mb-3">
@foreach (var variable in vars)
{
<div class="col">
<div class="card card-body border">
<label class="form-label">
<TL>Name</TL>
</label>
<div class="input-group mb-5">
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
</div>
<label class="form-label">
<TL>Value</TL>
</label>
<div class="input-group mb-5">
<input @bind="variable.Value" type="text" class="form-control">
</div>
</div>
</div>
}
</div>
}
</div>
}
</div>
</div>
<div class="row">
<div class="card card-body">
<div class="d-flex justify-content-end">
<a href="/admin/servers" class="btn btn-danger me-3">
<TL>Cancel</TL>
</a>
<button class="btn btn-success" type="submit">
<TL>Create</TL>
</button>
</div>
</div>
</div>
</SmartForm>
</LazyLoader>
@code
{
private ServerDataModel Model = new();
private List<Image> Images;
private Node[] Nodes;
private User[] Users;
private ServerVariable[] ServerVariables = Array.Empty<ServerVariable>();
private void RebuildVariables()
{
var list = new List<ServerVariable>();
if (Model.Image != null)
{
foreach (var variable in Model.Image.Variables)
{
list.Add(new()
{
Key = variable.Key,
Value = variable.DefaultValue
});
}
}
ServerVariables = list.ToArray();
}
private async Task Load(LazyLoader lazyLoader)
{
await lazyLoader.SetText("Loading images");
Images = ImageRepository
.Get()
.Include(x => x.Variables)
.Include(x => x.DockerImages)
.ToList();
await lazyLoader.SetText("Loading nodes");
Nodes = NodeRepository.Get().ToArray();
await lazyLoader.SetText("Loading users");
Users = UserRepository.Get().ToArray();
RebuildVariables();
}
private async Task Create()
{
try
{
var newServer = await ServerService.Create(Model.Name, Model.Cpu, Model.Memory, Model.Disk, Model.Owner, Model.Image, Model.Node, server =>
{
server.OverrideStartup = Model.OverrideStartup;
server.DockerImageIndex = Model.DockerImageIndex;
foreach (var serverVariable in ServerVariables)
{
server.Variables
.First(x => x.Key == serverVariable.Key).Value = serverVariable.Value;
}
});
await ToastService.Success(SmartTranslateService.Translate("Server successfully created"));
NavigationManager.NavigateTo($"/server/{newServer.Uuid}");
}
catch (DisplayException e)
{
await AlertService.Error(
SmartTranslateService.Translate("Error"),
SmartTranslateService.Translate(e.Message)
);
}
catch (Exception e)
{
Logger.Error("Error creating server");
Logger.Error(e);
await AlertService.Error(
SmartTranslateService.Translate("Error"),
SmartTranslateService.Translate("An unknown error occured")
);
}
}
private async Task OnChange()
{
RebuildVariables();
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,85 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using BlazorTable
@inject Repository<Server> ServerRepository
@inject Repository<NodeAllocation> NodeAllocationRepository
@inject AlertService AlertService
@inject SmartTranslateService SmartTranslateService
<div class="row">
<div class="col-12 col-md-6 mb-5">
<div class="card card-body">
<WButton Text="@(SmartTranslateService.Translate("Add allocation"))"
WorkingText="@(SmartTranslateService.Translate("Searching"))"
CssClasses="btn-primary"
OnClick="AddAllocation">
</WButton>
</div>
</div>
<div class="col-12 col-md-6">
<div class="card card-body">
<div class="table-responsive">
<table class="table table-bordered">
<tbody>
@foreach (var allocation in Server.Allocations)
{
<tr>
<td class="align-middle fs-5">
@(Server.Node.Fqdn + ":" + allocation.Port)
</td>
<td>
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
CssClasses="btn-danger"
OnClick="() => DeleteAllocation(allocation)">
</WButton>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
@code
{
[CascadingParameter]
public Server Server { get; set; }
private async Task AddAllocation()
{
// We have sadly no choice to use entity framework to do what the sql call does, there
// are only slower ways, so we will use a raw sql call as a exception
var freeAllocation = NodeAllocationRepository
.Get()
.FromSqlRaw($"SELECT * FROM `NodeAllocations` WHERE ServerId IS NULL AND NodeId={Server.Node.Id} LIMIT 1")
.FirstOrDefault();
if (freeAllocation == null)
{
await AlertService.Error(
SmartTranslateService.Translate("No free allocation found"));
return;
}
Server.Allocations.Add(freeAllocation);
ServerRepository.Update(Server);
await InvokeAsync(StateHasChanged);
}
private async Task DeleteAllocation(NodeAllocation nodeAllocation)
{
Server.Allocations.Remove(nodeAllocation);
ServerRepository.Update(Server);
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,68 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject ServerService ServerService
@inject SmartTranslateService SmartTranslateService
@inject AlertService AlertService
<div class="card">
<div class="card-body">
@if (Server.IsArchived)
{
<span class="fs-5 text-warning"><TL>Server is currently archived</TL></span>
}
else
{
<span class="fs-5"><TL>Server is currently not archived</TL></span>
}
</div>
<div class="card-footer">
<div class="text-end">
@if (Server.IsArchived)
{
<WButton Text="@(SmartTranslateService.Translate("Unarchive"))"
WorkingText="@(SmartTranslateService.Translate("Unarchiving"))"
CssClasses="btn-success"
OnClick="UnArchiveServer">
</WButton>
}
else
{
<WButton Text="@(SmartTranslateService.Translate("Archive"))"
WorkingText="@(SmartTranslateService.Translate("Archiving"))"
CssClasses="btn-danger"
OnClick="ArchiveServer">
</WButton>
}
</div>
</div>
</div>
@code
{
[CascadingParameter]
public Server Server { get; set; }
private async Task ArchiveServer()
{
await ServerService.ArchiveServer(Server);
await InvokeAsync(StateHasChanged);
await AlertService.Success(
SmartTranslateService.Translate("Successfully archived the server")
);
}
private async Task UnArchiveServer()
{
await ServerService.UnArchiveServer(Server);
await InvokeAsync(StateHasChanged);
await AlertService.Success(
SmartTranslateService.Translate("Successfully unarchived the server")
);
}
}

View File

@@ -1,47 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject ServerService ServerService
@inject SmartTranslateService SmartTranslateService
@inject MalwareScanService MalwareScanService
@inject AlertService AlertService
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Actions</TL>
</span>
</div>
<div class="card-footer">
<WButton Text="@(SmartTranslateService.Translate("Reinstall"))"
WorkingText="@(SmartTranslateService.Translate("Reinstalling"))" CssClasses="btn-warning"
OnClick="Reinstall">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Scan for malware"))"
WorkingText="@(SmartTranslateService.Translate("Scanning"))" CssClasses="btn-primary ms-3"
OnClick="Scan">
</WButton>
</div>
</div>
@code
{
[CascadingParameter]
public Server Server { get; set; }
private async Task Reinstall()
{
await ServerService.Reinstall(Server!);
}
private async Task Scan()
{
var result = await MalwareScanService.Perform(Server);
if (result == null)
await AlertService.Success(SmartTranslateService.Translate("No malware found on this server"));
else
await AlertService.Warning(result.Title, result.Description);
}
}

View File

@@ -1,111 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Models.Forms
@using Mappy.Net
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject Repository<Moonlight.App.Database.Entities.Image> ImageRepository
@inject Repository<Server> ServerRepository
@inject SmartTranslateService SmartTranslateService
@inject ToastService ToastService
<LazyLoader Load="Load">
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
<div class="card">
<div class="card-body p-10">
<label class="form-label">
<TL>Override startup command</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-terminal"></i>
</span>
<InputText @bind-Value="Model.OverrideStartup" type="text" class="form-control" placeholder="@(Server.Image.Startup)"></InputText>
</div>
<label class="form-label">
<TL>Docker image</TL>
</label>
<select @bind="Model.DockerImageIndex" class="form-select">
@foreach (var image in DockerImages)
{
<option value="@(DockerImages.IndexOf(image))">@(image.Name)</option>
}
</select>
</div>
<div class="card-body">
@foreach (var vars in Server.Variables.Chunk(4))
{
<div class="row mb-3">
@foreach (var variable in vars)
{
<div class="col">
<div class="card card-body">
<label class="form-label">
<TL>Name</TL>
</label>
<div class="input-group mb-5">
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
</div>
<label class="form-label">
<TL>Value</TL>
</label>
<div class="input-group mb-5">
<input @bind="variable.Value" type="text" class="form-control">
</div>
</div>
</div>
}
</div>
}
</div>
<div class="card-footer">
<div class="text-end">
<button type="submit" class="btn btn-success">
<TL>Save changes</TL>
</button>
</div>
</div>
</div>
</SmartForm>
</LazyLoader>
@code
{
[CascadingParameter]
public Server Server { get; set; }
private List<DockerImage> DockerImages;
private List<Moonlight.App.Database.Entities.Image> Images;
private ServerImageDataModel Model;
private Task Load(LazyLoader arg)
{
Images = ImageRepository
.Get()
.Include(x => x.Variables)
.Include(x => x.DockerImages)
.ToList();
DockerImages = Images
.First(x => x.Id == Server.Image.Id).DockerImages
.ToList();
Model = Mapper.Map<ServerImageDataModel>(Server);
return Task.CompletedTask;
}
private async Task OnValidSubmit()
{
Server = Mapper.Map(Server, Model);
ServerRepository.Update(Server);
await ToastService.Success(
SmartTranslateService.Translate("Successfully saved changes")
);
}
}

View File

@@ -1,81 +0,0 @@
@page "/admin/servers/view/{Id:int}/{Route?}"
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using Microsoft.EntityFrameworkCore
@using Moonlight.Shared.Components.Navigations
@inject Repository<Server> ServerRepository
<OnlyAdmin>
<LazyLoader @ref="LazyLoader" Load="Load">
@if (Server == null)
{
<div class="alert alert-danger">
<TL>No server with this id found</TL>
</div>
}
else
{
<CascadingValue TValue="Server" Value="Server">
<SmartRouter Route="@Route">
<Route Path="/">
<AdminServersViewNavigation Index="0" Server="Server"/>
<Overview/>
</Route>
<Route Path="/image">
<AdminServersViewNavigation Index="1" Server="Server"/>
<Image/>
</Route>
<Route Path="/resources">
<AdminServersViewNavigation Index="2" Server="Server"/>
<Resources/>
</Route>
<Route Path="/allocations">
<AdminServersViewNavigation Index="3" Server="Server"/>
<Allocations />
</Route>
<Route Path="/archive">
<AdminServersViewNavigation Index="4" Server="Server"/>
<Archive/>
</Route>
<Route Path="/debug">
<AdminServersViewNavigation Index="5" Server="Server"/>
<Debug/>
</Route>
<Route Path="/delete">
<AdminServersViewNavigation Index="6" Server="Server"/>
</Route>
</SmartRouter>
</CascadingValue>
}
</LazyLoader>
</OnlyAdmin>
@code
{
[Parameter]
public string? Route { get; set; }
[Parameter]
public int Id { get; set; }
private LazyLoader LazyLoader;
private Server? Server;
private Task Load(LazyLoader arg)
{
Server = ServerRepository
.Get()
.Include(x => x.Image)
.Include(x => x.Owner)
.Include(x => x.Archive)
.Include(x => x.Allocations)
.Include(x => x.MainAllocation)
.Include(x => x.Variables)
.Include(x => x.Node)
.FirstOrDefault(x => x.Id == Id);
return Task.CompletedTask;
}
}

View File

@@ -1,91 +0,0 @@
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Forms
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Mappy.Net
@inject Repository<User> UserRepository
@inject Repository<Server> ServerRepository
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService
<LazyLoader Load="Load">
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
<div class="card">
<div class="card-body p-10">
<label class="form-label">
<TL>Identifier</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-id-card"></i>
</span>
<input type="number" class="form-control disabled" disabled="" value="@(Server.Id)">
</div>
<label class="form-label">
<TL>UuidIdentifier</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-id-card"></i>
</span>
<input type="text" class="form-control disabled" disabled="" value="@(Server.Uuid)">
</div>
<label class="form-label">
<TL>Server name</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-purchase-tag-alt"></i>
</span>
<InputText @bind-Value="Model.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Server name"))"></InputText>
</div>
<label class="form-label">
<TL>Owner</TL>
</label>
<div class="input-group mb-5">
<SmartDropdown T="User"
@bind-Value="Model.Owner"
Items="Users"
DisplayFunc="@(x => x.Email)"
SearchProp="@(x => x.Email)">
</SmartDropdown>
</div>
</div>
<div class="card-footer">
<div class="text-end">
<button type="submit" class="btn btn-success"><TL>Save changes</TL></button>
</div>
</div>
</div>
</SmartForm>
</LazyLoader>
@code
{
[CascadingParameter]
public Server Server { get; set; }
private ServerOverviewDataModel Model;
private User[] Users;
private Task Load(LazyLoader arg)
{
Users = UserRepository.Get().ToArray();
Model = Mapper.Map<ServerOverviewDataModel>(Server);
return Task.CompletedTask;
}
private async Task OnValidSubmit()
{
Server = Mapper.Map(Server, Model);
ServerRepository.Update(Server);
await ToastService.Success(
SmartTranslateService.Translate("Successfully saved changes")
);
}
}

View File

@@ -1,88 +0,0 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Forms
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Mappy.Net
@inject Repository<Server> ServerRepository
@inject SmartTranslateService SmartTranslateService
@inject ToastService ToastService
<LazyLoader Load="Load">
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
<div class="card">
<div class="card-body p-10">
<label class="form-label">
<TL>Cpu cores</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-chip"></i>
</span>
<InputNumber @bind-Value="Model.Cpu" type="number" class="form-control"></InputNumber>
<span class="input-group-text">
<TL>CPU Cores (100% = 1 Core)</TL>
</span>
</div>
<label class="form-label">
<TL>Memory</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-microchip"></i>
</span>
<InputNumber @bind-Value="Model.Memory" type="number" class="form-control"></InputNumber>
<span class="input-group-text">
MB
</span>
</div>
<label class="form-label">
<TL>Disk</TL>
</label>
<div class="input-group mb-5">
<span class="input-group-text">
<i class="bx bx-hdd"></i>
</span>
<InputNumber @bind-Value="Model.Disk" type="number" class="form-control"></InputNumber>
<span class="input-group-text">
MB
</span>
</div>
</div>
<div class="card-footer">
<div class="text-end">
<button type="submit" class="btn btn-success">
<TL>Save changes</TL>
</button>
</div>
</div>
</div>
</SmartForm>
</LazyLoader>
@code
{
[CascadingParameter]
public Server Server { get; set; }
private ServerResourcesDataModel Model;
private Task Load(LazyLoader arg)
{
Model = Mapper.Map<ServerResourcesDataModel>(Server);
return Task.CompletedTask;
}
private async Task OnValidSubmit()
{
Server = Mapper.Map(Server, Model);
ServerRepository.Update(Server);
await ToastService.Success(
SmartTranslateService.Translate("Successfully saved changes")
);
}
}

View File

@@ -1,193 +0,0 @@
@page "/admin/statistics"
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services.Statistics
@using Moonlight.App.Database.Entities
@using ApexCharts
@using Moonlight.App.Helpers
@using Moonlight.App.Services
@using Moonlight.Shared.Components.Navigations
@inject StatisticsViewService StatisticsViewService
@inject SmartTranslateService SmartTranslateService
@attribute [PermissionRequired(nameof(Permissions.AdminStatistics))]
<AdminStatisticsNavigation Index="0" />
<div class="row mb-2">
<div class="col-12 col-lg-6 col-xl">
<div class="card card-body">
<select class="form-select" @bind="TimeSpanBind">
<option value="1">
<TL>Hour</TL>
</option>
<option value="24">
<TL>Day</TL>
</option>
<option value="744">
<TL>Month</TL>
</option>
<option value="8760">
<TL>Year</TL>
</option>
<option value="867240">
<TL>All time</TL>
</option>
</select>
</div>
</div>
</div>
<LazyLoader @ref="Loader" Load="Load">
@foreach (var charts in Charts.Chunk(2))
{
<div class="row">
@foreach (var chart in charts)
{
<div class="col-sm-6">
<div class="card mt-4">
<div class="card-header">
<div class="card-title">
<TL>@chart.Key</TL>
</div>
</div>
<div class="card-body">
<ApexChart TItem="StatisticsData"
Options="GenerateOptions()"
OnRendered="OnChartRendered">
<ApexPointSeries TItem="StatisticsData"
Items="chart.Value"
SeriesType="SeriesType.Area"
Name=""
ShowDataLabels="false"
XValue="@(e => Formatter.FormatDate(e.Date))"
YValue="@(e => (decimal)Math.Round(e.Value))"/>
</ApexChart>
</div>
</div>
</div>
}
</div>
}
<div class="row">
<div class="col-sm-6">
<div class="card mt-4">
<div class="card-header">
<div class="card-title">
<TL>Active users</TL>
</div>
</div>
<div class="card-body">
<span class="fs-2">@(ActiveUsers)</span>
</div>
</div>
</div>
</div>
</LazyLoader>
@code
{
private StatisticsTimeSpan StatisticsTimeSpan = StatisticsTimeSpan.Day;
private LazyLoader Loader;
private Dictionary<string, StatisticsData[]> Charts = new();
private int ActiveUsers = 0;
private int TimeSpanBind
{
get => (int)StatisticsTimeSpan;
set
{
StatisticsTimeSpan = (StatisticsTimeSpan)value;
Task.Run(async () => await Loader.Reload());
}
}
private Task Load(LazyLoader loader)
{
Charts.Clear();
Charts.Add(
SmartTranslateService.Translate("Servers"),
AvgHelper.Calculate(
StatisticsViewService.GetData("serversCount", StatisticsTimeSpan)
)
);
Charts.Add(
SmartTranslateService.Translate("Users"),
AvgHelper.Calculate(
StatisticsViewService.GetData("usersCount", StatisticsTimeSpan)
)
);
Charts.Add(
SmartTranslateService.Translate("Domains"),
AvgHelper.Calculate(
StatisticsViewService.GetData("domainsCount", StatisticsTimeSpan)
)
);
Charts.Add(
SmartTranslateService.Translate("Databases"),
AvgHelper.Calculate(
StatisticsViewService.GetData("databasesCount", StatisticsTimeSpan)
)
);
Charts.Add(
SmartTranslateService.Translate("Webspaces"),
AvgHelper.Calculate(
StatisticsViewService.GetData("webspacesCount", StatisticsTimeSpan)
)
);
Charts.Add(
SmartTranslateService.Translate("Sessions"),
AvgHelper.Calculate(
StatisticsViewService.GetData("sessionsCount", StatisticsTimeSpan)
)
);
ActiveUsers = StatisticsViewService.GetActiveUsers(StatisticsTimeSpan);
return Task.CompletedTask;
}
private ApexChartOptions<StatisticsData> GenerateOptions()
{
return new()
{
Legend = new Legend()
{
Show = false
},
DataLabels = new DataLabels()
{
Enabled = false
},
Xaxis = new XAxis()
{
Labels = new XAxisLabels()
{
Show = false
}
},
Chart = new Chart()
{
RedrawOnParentResize = true,
Toolbar = new Toolbar()
{
Show = false
},
Height = 300
}
};
}
private Task OnChartRendered()
{
return Task.CompletedTask;
}
}

View File

@@ -1,204 +0,0 @@
@page "/admin/statistics/live"
@using Moonlight.App.Services
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.App.Services.Sessions
@using Moonlight.Shared.Components.Navigations
@inject NodeService NodeService
@inject Repository<Node> NodeRepository
@inject IServiceScopeFactory ServiceScopeFactory
@attribute [PermissionRequired(nameof(Permissions.AdminStatisticsLive))]
<AdminStatisticsNavigation Index="1" />
<div class="row">
<div class="col-12 col-md-3 mb-3">
<div class="card">
<div class="card-header pt-5">
<div class="card-title d-flex flex-column">
<span class="fs-2hx fw-bold text-white me-2 lh-1 ls-n2">@(Math.Round(TotalCpuUsed, 2))% / 100%</span>
<span class="text-white opacity-75 pt-1 fw-semibold fs-6">
<TL>Total cpu load</TL>
</span>
</div>
</div>
<div class="card-body d-flex align-items-end pt-0">
<div class="d-flex align-items-center flex-column mt-3 w-100">
@{
var cpuPercent = Math.Round(Formatter.CalculatePercentage(TotalCpuUsed, 100));
}
<div class="d-flex justify-content-end fw-bold fs-6 text-white opacity-75 w-100 mt-auto mb-2">
<span>@(cpuPercent)%</span>
</div>
<div class="h-8px mx-3 w-100 bg-white bg-opacity-50 rounded">
<div class="bg-@(GetStateColor(cpuPercent)) rounded h-8px" role="progressbar" style="width: @(cpuPercent)%;" aria-valuenow="@(cpuPercent)" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-3 mb-3">
<div class="card">
<div class="card-header pt-5">
<div class="card-title d-flex flex-column">
<span class="fs-2hx fw-bold text-white me-2 lh-1 ls-n2">@(ByteSizeValue.FromKiloBytes(TotalMemoryUsed).GigaBytes)GB / @(ByteSizeValue.FromKiloBytes(TotalMemory).GigaBytes)GB</span>
<span class="text-white opacity-75 pt-1 fw-semibold fs-6">
<TL>Total memory load</TL>
</span>
</div>
</div>
<div class="card-body d-flex align-items-end pt-0">
<div class="d-flex align-items-center flex-column mt-3 w-100">
@{
var memoryPercent = Math.Round(Formatter.CalculatePercentage(TotalMemoryUsed, TotalMemory));
}
<div class="d-flex justify-content-end fw-bold fs-6 text-white opacity-75 w-100 mt-auto mb-2">
<span>@(memoryPercent)%</span>
</div>
<div class="h-8px mx-3 w-100 bg-white bg-opacity-50 rounded">
<div class="bg-@(GetStateColor(memoryPercent)) rounded h-8px" role="progressbar" style="width: @(memoryPercent)%;" aria-valuenow="@(memoryPercent)" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-3 mb-3">
<div class="card">
<div class="card-body pt-5">
<div class="card-title d-flex flex-column">
<span class="fs-2hx fw-bold text-white me-2 lh-1 ls-n2">@(Users)</span>
<span class="text-white opacity-75 pt-1 fw-semibold fs-6">
<TL>Total user count</TL>
</span>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-3 mb-3">
<div class="card">
<div class="card-body pt-5">
<div class="card-title d-flex flex-column">
<span class="fs-2hx fw-bold text-white me-2 lh-1 ls-n2">@(Sessions)</span>
<span class="text-white opacity-75 pt-1 fw-semibold fs-6">
<TL>Total session count</TL>
</span>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-3 mb-3">
<div class="card">
<div class="card-body pt-5">
<div class="card-title d-flex flex-column">
<span class="fs-2hx fw-bold text-white me-2 lh-1 ls-n2">@(ActiveUsers)</span>
<span class="text-white opacity-75 pt-1 fw-semibold fs-6">
<TL>Total active user count</TL>
</span>
</div>
</div>
</div>
</div>
</div>
@code
{
private long TotalMemoryUsed;
private long TotalMemory;
private double TotalCpuUsed;
private int Users;
private int ActiveUsers;
private int Sessions;
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Task.Run(async () =>
{
while (true)
{
await Monitor();
await Task.Delay(TimeSpan.FromSeconds(5));
}
});
}
return Task.CompletedTask;
}
private async Task Monitor()
{
async Task Nodes()
{
TotalMemory = 0;
TotalMemoryUsed = 0;
var cpuValues = new List<double>();
foreach (var node in NodeRepository.Get().ToArray())
{
try
{
var metrics = await NodeService.GetMemoryMetrics(node);
TotalMemory += metrics.Total;
TotalMemoryUsed += metrics.Used;
var cpuMetrics = await NodeService.GetCpuMetrics(node);
cpuValues.Add(cpuMetrics.CpuUsage);
}
catch (Exception)
{
// ignored
}
}
TotalCpuUsed = Formatter.CalculateAverage(cpuValues);
await InvokeAsync(StateHasChanged);
}
async Task UsersAndSessions()
{
using var scope = ServiceScopeFactory.CreateScope();
var userRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
var sessionService = scope.ServiceProvider.GetRequiredService<SessionServerService>();
Users = userRepo.Get().Count();
Sessions = (await sessionService.GetSessions()).Length;
ActiveUsers = userRepo
.Get()
.Count(x => x.LastVisitedAt > DateTime.UtcNow.AddDays(-1));
await InvokeAsync(StateHasChanged);
}
await Nodes();
await UsersAndSessions();
}
private string GetStateColor(double percent)
{
if (percent < 60)
return "success";
else if (percent >= 60 && percent < 80)
return "warning";
else
return "danger";
}
}

View File

@@ -1,196 +0,0 @@
@page "/admin/subscriptions/edit/{Id:int}"
@using Moonlight.App.Models.Forms
@using Moonlight.App.Models.Misc
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@using Moonlight.App.Database.Entities
@using Mappy.Net
@inject NavigationManager NavigationManager
@inject SubscriptionRepository SubscriptionRepository
@inject SubscriptionService SubscriptionService
@attribute [PermissionRequired(nameof(Permissions.AdminSubscriptionEdit))]
<div class="card card-body p-10">
<LazyLoader Load="Load">
@if (Subscription == null)
{
<div class="alert alert-danger">
No subscription with this id has been found
</div>
}
else
{
<SmartForm Model="Model" OnValidSubmit="OnSubmit">
<label class="form-label">
<TL>Name</TL>
</label>
<div class="input-group mb-5">
<InputText @bind-Value="Model.Name" class="form-control"></InputText>
</div>
<label class="form-label">
<TL>Description</TL>
</label>
<div class="input-group mb-5">
<InputTextArea @bind-Value="Model.Description" class="form-control"></InputTextArea>
</div>
<label class="form-label">
<TL>Price</TL>
</label>
<div class="input-group mb-5">
<InputNumber @bind-Value="Model.Price" class="form-control"></InputNumber>
</div>
<label class="form-label">
<TL>Currency</TL>
</label>
<div class="input-group mb-5">
<select @bind="Model.Currency" class="form-select">
@foreach (var currency in (Currency[])Enum.GetValues(typeof(Currency)))
{
if (Model.Currency == currency)
{
<option value="@(currency)" selected="">@(currency)</option>
}
else
{
<option value="@(currency)">@(currency)</option>
}
}
</select>
</div>
<label class="form-label">
<TL>Duration</TL>
</label>
<div class="input-group mb-5">
<InputNumber @bind-Value="Model.Duration" class="form-control"></InputNumber>
</div>
<div>
@foreach (var limitPart in Limits.Chunk(3))
{
<div class="row row-cols-3 mb-5">
@foreach (var limit in limitPart)
{
<div class="col">
<div class="card card-body border">
<label class="form-label">
<TL>Identifier</TL>
</label>
<div class="input-group mb-5">
<input @bind="limit.Identifier" type="text" class="form-control">
</div>
<label class="form-label">
<TL>Amount</TL>
</label>
<div class="input-group mb-5">
<input @bind="limit.Amount" type="number" class="form-control">
</div>
<div class="d-flex flex-column mb-15 fv-row">
<div class="fs-5 fw-bold form-label mb-3">
<TL>Options</TL>
</div>
<div class="table-responsive">
<div class="dataTables_wrapper dt-bootstrap4 no-footer">
<div class="table-responsive">
<table class="table align-middle table-row-dashed fw-semibold fs-6 gy-5 dataTable no-footer">
<thead>
<tr class="text-start text-muted fw-bold fs-7 text-uppercase gs-0">
<th class="pt-0 sorting_disabled">
<TL>Key</TL>
</th>
<th class="pt-0 sorting_disabled">
<TL>Value</TL>
</th>
<th class="pt-0 text-end sorting_disabled">
<TL>Remove</TL>
</th>
</tr>
</thead>
<tbody>
@foreach (var option in limit.Options)
{
<tr class="odd">
<td>
<input @bind="option.Key" type="text" class="form-control form-control-solid">
</td>
<td>
<input @bind="option.Value" type="text" class="form-control form-control-solid">
</td>
<td class="text-end">
<button @onclick="() => limit.Options.Remove(option)" type="button" class="btn btn-icon btn-flex btn-active-light-primary w-30px h-30px me-3" data-kt-action="field_remove">
<i class="bx bx-trash"></i>
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
<div class="row">
<div class="col-sm-12 col-md-5 d-flex align-items-center justify-content-center justify-content-md-start"></div>
<div class="col-sm-12 col-md-7 d-flex align-items-center justify-content-center justify-content-md-end"></div>
</div>
</div>
</div>
<div class="btn-group mt-5">
<button @onclick:preventDefault @onclick="() => limit.Options.Add(new())" type="button" class="btn btn-light-primary me-auto">Add option</button>
<button @onclick:preventDefault @onclick="() => Limits.Remove(limit)" class="btn btn-danger float-end">
<i class="bx bx-trash"></i>
</button>
</div>
</div>
</div>
</div>
}
</div>
}
</div>
<div class="float-end">
<button @onclick:preventDefault @onclick="() => Limits.Add(new())" class="btn btn-primary">
<TL>Add new limit</TL>
</button>
<button type="submit" class="btn btn-success">
<TL>Save subscription</TL>
</button>
</div>
</SmartForm>
}
</LazyLoader>
</div>
@code
{
[Parameter]
public int Id { get; set; }
private Subscription? Subscription;
private SubscriptionDataModel Model = new();
private List<SubscriptionLimit> Limits = new();
private async Task OnSubmit()
{
Subscription = Mapper.Map(Subscription, Model);
await SubscriptionService.Update(Subscription!);
await SubscriptionService.UpdateLimits(Subscription!, Limits.ToArray());
NavigationManager.NavigateTo("/admin/subscriptions");
}
private async Task Load(LazyLoader arg)
{
Subscription = SubscriptionRepository
.Get()
.FirstOrDefault(x => x.Id == Id);
if (Subscription != null)
{
Model = Mapper.Map<SubscriptionDataModel>(Subscription);
Limits = (await SubscriptionService.GetLimits(Subscription)).ToList();
}
}
}

View File

@@ -1,78 +0,0 @@
@page "/admin/subscriptions/"
@using Moonlight.App.Services
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories
@using BlazorTable
@using Moonlight.App.Services.Interop
@inject SmartTranslateService SmartTranslateService
@inject SubscriptionRepository SubscriptionRepository
@inject SubscriptionService SubscriptionService
@attribute [PermissionRequired(nameof(Permissions.AdminSubscriptions))]
<div class="card">
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
<span class="card-label fw-bold fs-3 mb-1">
<TL>Subscriptions</TL>
</span>
</h3>
<div class="card-toolbar">
<a href="/admin/subscriptions/new" class="btn btn-sm btn-light-success">
<i class="bx bx-credit-card"></i>
<TL>New subscription</TL>
</a>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<Table TableItem="Subscription" Items="Subscriptions" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Description"))" Field="@(x => x.Description)" Sortable="true" Filterable="true"/>
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Price"))" Field="@(x => x.Price)" Sortable="true" Filterable="true"/>
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Currency"))" Field="@(x => x.Currency)" Sortable="true" Filterable="true"/>
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Duration"))" Field="@(x => x.Duration)" Sortable="true" Filterable="true"/>
<Column TableItem="Subscription" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<a href="/admin/subscriptions/edit/@(context.Id)/">
<TL>Manage</TL>
</a>
</Template>
</Column>
<Column TableItem="Subscription" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<DeleteButton Confirm="true"
OnClick="() => Delete(context)">
</DeleteButton>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</div>
</LazyLoader>
</div>
@code
{
private Subscription[] Subscriptions;
private LazyLoader LazyLoader;
private Task Load(LazyLoader arg)
{
Subscriptions = SubscriptionRepository
.Get()
.ToArray();
return Task.CompletedTask;
}
private async Task Delete(Subscription subscription)
{
await SubscriptionService.Delete(subscription);
await LazyLoader.Reload();
}
}

View File

@@ -1,169 +0,0 @@
@page "/admin/subscriptions/new"
@using Moonlight.App.Models.Forms
@using Moonlight.App.Models.Misc
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@inject NavigationManager NavigationManager
@inject SubscriptionRepository SubscriptionRepository
@inject SubscriptionService SubscriptionService
@attribute [PermissionRequired(nameof(Permissions.AdminNewSubscription))]
<div class="card card-body p-10">
<SmartForm Model="Model" OnValidSubmit="OnSubmit">
<label class="form-label">
<TL>Name</TL>
</label>
<div class="input-group mb-5">
<InputText @bind-Value="Model.Name" class="form-control"></InputText>
</div>
<label class="form-label">
<TL>Description</TL>
</label>
<div class="input-group mb-5">
<InputTextArea @bind-Value="Model.Description" class="form-control"></InputTextArea>
</div>
<label class="form-label">
<TL>Price</TL>
</label>
<div class="input-group mb-5">
<InputNumber @bind-Value="Model.Price" class="form-control"></InputNumber>
</div>
<label class="form-label">
<TL>Currency</TL>
</label>
<div class="input-group mb-5">
<select @bind="Model.Currency" class="form-select">
@foreach (var currency in (Currency[])Enum.GetValues(typeof(Currency)))
{
if (Model.Currency == currency)
{
<option value="@(currency)" selected="">@(currency)</option>
}
else
{
<option value="@(currency)">@(currency)</option>
}
}
</select>
</div>
<label class="form-label">
<TL>Duration</TL>
</label>
<div class="input-group mb-5">
<InputNumber @bind-Value="Model.Duration" class="form-control"></InputNumber>
</div>
<div>
@foreach (var limitPart in Limits.Chunk(3))
{
<div class="row row-cols-3 mb-5">
@foreach (var limit in limitPart)
{
<div class="col">
<div class="card card-body border">
<label class="form-label">
<TL>Identifier</TL>
</label>
<div class="input-group mb-5">
<input @bind="limit.Identifier" type="text" class="form-control">
</div>
<label class="form-label">
<TL>Amount</TL>
</label>
<div class="input-group mb-5">
<input @bind="limit.Amount" type="number" class="form-control">
</div>
<div class="d-flex flex-column mb-15 fv-row">
<div class="fs-5 fw-bold form-label mb-3">
<TL>Options</TL>
</div>
<div class="table-responsive">
<div class="dataTables_wrapper dt-bootstrap4 no-footer">
<div class="table-responsive">
<table class="table align-middle table-row-dashed fw-semibold fs-6 gy-5 dataTable no-footer">
<thead>
<tr class="text-start text-muted fw-bold fs-7 text-uppercase gs-0">
<th class="pt-0 sorting_disabled">
<TL>Key</TL>
</th>
<th class="pt-0 sorting_disabled">
<TL>Value</TL>
</th>
<th class="pt-0 text-end sorting_disabled">
<TL>Remove</TL>
</th>
</tr>
</thead>
<tbody>
@foreach (var option in limit.Options)
{
<tr class="odd">
<td>
<input @bind="option.Key" type="text" class="form-control form-control-solid">
</td>
<td>
<input @bind="option.Value" type="text" class="form-control form-control-solid">
</td>
<td class="text-end">
<button @onclick="() => limit.Options.Remove(option)" type="button" class="btn btn-icon btn-flex btn-active-light-primary w-30px h-30px me-3" data-kt-action="field_remove">
<i class="bx bx-trash"></i>
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
<div class="row">
<div class="col-sm-12 col-md-5 d-flex align-items-center justify-content-center justify-content-md-start"></div>
<div class="col-sm-12 col-md-7 d-flex align-items-center justify-content-center justify-content-md-end"></div>
</div>
</div>
</div>
<div class="btn-group mt-5">
<button @onclick:preventDefault @onclick="() => limit.Options.Add(new())" type="button" class="btn btn-light-primary me-auto">Add option</button>
<button @onclick:preventDefault @onclick="() => Limits.Remove(limit)" class="btn btn-danger float-end">
<i class="bx bx-trash"></i>
</button>
</div>
</div>
</div>
</div>
}
</div>
}
</div>
<div class="float-end">
<button @onclick:preventDefault @onclick="() => Limits.Add(new())" class="btn btn-primary">
<TL>Add new limit</TL>
</button>
<button type="submit" class="btn btn-success">
<TL>Create subscription</TL>
</button>
</div>
</SmartForm>
</div>
@code
{
private SubscriptionDataModel Model = new();
private List<SubscriptionLimit> Limits = new();
private async Task OnSubmit()
{
var sub = await SubscriptionService.Create(
Model.Name,
Model.Description,
Model.Currency,
Model.Price,
Model.Duration
);
await SubscriptionService.UpdateLimits(sub, Limits.ToArray());
NavigationManager.NavigateTo("/admin/subscriptions");
}
}

View File

@@ -1,278 +0,0 @@
@page "/admin/support"
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc
@using Microsoft.EntityFrameworkCore
@using BlazorTable
@using Moonlight.App.Helpers
@using Moonlight.App.Services
@using Moonlight.App.Services.Sessions
@inject Repository<Ticket> TicketRepository
@inject SmartTranslateService SmartTranslateService
@inject IdentityService IdentityService
@attribute [PermissionRequired(nameof(Permissions.AdminSupport))]
<div class="row mb-5">
<LazyLoader Load="LoadStatistics">
<div class="col-12 col-lg-6 col-xl">
<div class="mt-4 card">
<div class="card-body">
<div class="row align-items-center gx-0">
<div class="col">
<h6 class="text-uppercase text-muted mb-2">
<TL>Total Tickets</TL>
</h6>
<span class="h2 mb-0">
@(TotalTicketCount)
</span>
</div>
<div class="col-auto">
<span class="h2 text-muted mb-0">
<i class="text-primary bx bx-purchase-tag bx-lg"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6 col-xl">
<div class="mt-4 card">
<div class="card-body">
<div class="row align-items-center gx-0">
<div class="col">
<h6 class="text-uppercase text-muted mb-2">
<TL>Unassigned tickets</TL>
</h6>
<span class="h2 mb-0">
@(UnAssignedTicketCount)
</span>
</div>
<div class="col-auto">
<span class="h2 text-muted mb-0">
<i class="text-primary bx bxs-bell-ring bx-lg"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6 col-xl">
<div class="mt-4 card">
<div class="card-body">
<div class="row align-items-center gx-0">
<div class="col">
<h6 class="text-uppercase text-muted mb-2">
<TL>Pending tickets</TL>
</h6>
<span class="h2 mb-0">
@(PendingTicketCount)
</span>
</div>
<div class="col-auto">
<span class="h2 text-muted mb-">
<i class="text-primary bx bx-hourglass bx-lg"></i>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6 col-xl">
<div class="mt-4 card">
<div class="card-body">
<div class="row align-items-center gx-0">
<div class="col">
<h6 class="text-uppercase text-muted mb-2">
<TL>Closed tickets</TL>
</h6>
<span class="h2 mb-0">
@(ClosedTicketCount)
</span>
</div>
<div class="col-auto">
<span class="h2 text-muted mb-">
<i class="text-primary bx bx-lock bx-lg"></i>
</span>
</div>
</div>
</div>
</div>
</div>
</LazyLoader>
</div>
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Ticket overview</TL>
</span>
<div class="card-toolbar">
<div class="btn-group">
<WButton Text="@(SmartTranslateService.Translate("Overview"))" CssClasses="btn-secondary" OnClick="() => UpdateFilter(0)" />
<WButton Text="@(SmartTranslateService.Translate("Unassigned tickets"))" CssClasses="btn-secondary" OnClick="() => UpdateFilter(1)" />
<WButton Text="@(SmartTranslateService.Translate("My tickets"))" CssClasses="btn-secondary" OnClick="() => UpdateFilter(2)" />
<WButton Text="@(SmartTranslateService.Translate("All tickets"))" CssClasses="btn-secondary" OnClick="() => UpdateFilter(3)" />
</div>
</div>
</div>
<div class="card-body">
<LazyLoader @ref="TicketLazyLoader" Load="LoadTickets">
<div class="table-responsive">
<Table TableItem="Ticket" Items="AllTickets" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="Ticket" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Filterable="true" Sortable="true"/>
<Column TableItem="Ticket" Title="@(SmartTranslateService.Translate("Assigned to"))" Field="@(x => x.AssignedTo)" Filterable="true" Sortable="true">
<Template>
<span>@(context.AssignedTo == null ? "None" : context.AssignedTo.FirstName + " " + context.AssignedTo.LastName)</span>
</Template>
</Column>
<Column TableItem="Ticket" Title="@(SmartTranslateService.Translate("Ticket title"))" Field="@(x => x.IssueTopic)" Filterable="true" Sortable="false"/>
<Column TableItem="Ticket" Title="@(SmartTranslateService.Translate("User"))" Field="@(x => x.CreatedBy)" Filterable="true" Sortable="true">
<Template>
<span>@(context.CreatedBy.FirstName) @(context.CreatedBy.LastName)</span>
</Template>
</Column>
<Column TableItem="Ticket" Title="@(SmartTranslateService.Translate("Created at"))" Field="@(x => x.CreatedAt)" Filterable="true" Sortable="true">
<Template>
<span>@(Formatter.FormatDate(context.CreatedAt))</span>
</Template>
</Column>
<Column TableItem="Ticket" Title="@(SmartTranslateService.Translate("Priority"))" Field="@(x => x.Priority)" Filterable="true" Sortable="true">
<Template>
@switch (context.Priority)
{
case TicketPriority.Low:
<span class="badge bg-success">@(context.Priority)</span>
break;
case TicketPriority.Medium:
<span class="badge bg-primary">@(context.Priority)</span>
break;
case TicketPriority.High:
<span class="badge bg-warning">@(context.Priority)</span>
break;
case TicketPriority.Critical:
<span class="badge bg-danger">@(context.Priority)</span>
break;
}
</Template>
</Column>
<Column TableItem="Ticket" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Status)" Filterable="true" Sortable="true">
<Template>
@switch (context.Status)
{
case TicketStatus.Closed:
<span class="badge bg-danger">@(context.Status)</span>
break;
case TicketStatus.Open:
<span class="badge bg-success">@(context.Status)</span>
break;
case TicketStatus.Pending:
<span class="badge bg-warning">@(context.Status)</span>
break;
case TicketStatus.WaitingForUser:
<span class="badge bg-primary">@(context.Status)</span>
break;
}
</Template>
</Column>
<Column TableItem="Ticket" Title="" Field="@(x => x.Id)" Filterable="false" Sortable="false">
<Template>
<a class="btn btn-sm btn-primary" href="/admin/support/view/@(context.Id)">
<TL>Open</TL>
</a>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
@code
{
private int TotalTicketCount;
private int UnAssignedTicketCount;
private int PendingTicketCount;
private int ClosedTicketCount;
private Ticket[] AllTickets;
private int Filter = 0;
private LazyLoader TicketLazyLoader;
private Task LoadStatistics(LazyLoader _)
{
TotalTicketCount = TicketRepository
.Get()
.Count();
UnAssignedTicketCount = TicketRepository
.Get()
.Include(x => x.AssignedTo)
.Where(x => x.Status != TicketStatus.Closed)
.Count(x => x.AssignedTo == null);
PendingTicketCount = TicketRepository
.Get()
.Include(x => x.AssignedTo)
.Where(x => x.AssignedTo != null)
.Count(x => x.Status != TicketStatus.Closed);
ClosedTicketCount = TicketRepository
.Get()
.Count(x => x.Status == TicketStatus.Closed);
return Task.CompletedTask;
}
private Task LoadTickets(LazyLoader _)
{
switch (Filter)
{
default:
AllTickets = TicketRepository
.Get()
.Include(x => x.CreatedBy)
.Include(x => x.AssignedTo)
.Where(x => x.Status != TicketStatus.Closed)
.ToArray();
break;
case 1:
AllTickets = TicketRepository
.Get()
.Include(x => x.CreatedBy)
.Include(x => x.AssignedTo)
.Where(x => x.AssignedTo == null)
.Where(x => x.Status != TicketStatus.Closed)
.ToArray();
break;
case 2:
AllTickets = TicketRepository
.Get()
.Include(x => x.CreatedBy)
.Include(x => x.AssignedTo)
.Where(x => x.AssignedTo != null)
.Where(x => x.AssignedTo!.Id == IdentityService.User.Id)
.Where(x => x.Status != TicketStatus.Closed)
.ToArray();
break;
case 3:
AllTickets = TicketRepository
.Get()
.Include(x => x.CreatedBy)
.Include(x => x.AssignedTo)
.ToArray();
break;
}
return Task.CompletedTask;
}
private async Task UpdateFilter(int filterId)
{
Filter = filterId;
await TicketLazyLoader.Reload();
}
}

View File

@@ -1,379 +0,0 @@
@page "/old_admin/support"
@page "/old_admin/support/{Id:int}"
@using Moonlight.App.Services.Tickets
@using Moonlight.App.Database.Entities
@using Moonlight.App.Events
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services
@using Moonlight.App.Services.Sessions
@using Moonlight.Shared.Components.Tickets
@inject TicketAdminService AdminService
@inject SmartTranslateService SmartTranslateService
@inject EventSystem EventSystem
@inject IdentityService IdentityService
@attribute [PermissionRequired(nameof(Permissions.AdminSupport))]
@implements IDisposable
<div class="d-flex flex-column flex-lg-row">
<div class="flex-column flex-lg-row-auto w-100 w-lg-300px w-xl-400px mb-10 mb-lg-0">
<div class="card card-flush">
<div class="card-body pt-5">
<div class="scroll-y me-n5 pe-5 h-200px h-lg-auto" data-kt-scroll="true" data-kt-scroll-activate="{default: false, lg: true}" data-kt-scroll-max-height="auto" data-kt-scroll-dependencies="#kt_header, #kt_app_header, #kt_toolbar, #kt_app_toolbar, #kt_footer, #kt_app_footer, #kt_chat_contacts_header" data-kt-scroll-wrappers="#kt_content, #kt_app_content, #kt_chat_contacts_body" data-kt-scroll-offset="5px" style="max-height: 601px;">
<div class="separator separator-content border-primary mb-10 mt-5">
<span class="w-250px fw-bold fs-5">
<TL>Unassigned tickets</TL>
</span>
</div>
@foreach (var ticket in UnAssignedTickets)
{
<div class="d-flex flex-stack py-4">
<div class="d-flex align-items-center">
<div class="ms-5">
<a href="/admin/support/@(ticket.Key.Id)" class="fs-5 fw-bold text-gray-900 text-hover-primary mb-2">@(ticket.Key.IssueTopic)</a>
@if (ticket.Value != null)
{
<div class="fw-semibold text-muted">
@(ticket.Value.Content)
</div>
}
</div>
</div>
<div class="d-flex flex-column align-items-end ms-2">
@if (ticket.Value != null)
{
<span class="text-muted fs-7 mb-1">
@(Formatter.FormatAgoFromDateTime(ticket.Value.CreatedAt, SmartTranslateService))
</span>
}
</div>
</div>
if (ticket.Key != UnAssignedTickets.Last().Key)
{
<div class="separator"></div>
}
}
@if (AssignedTickets.Any())
{
<div class="separator separator-content border-primary mb-5 mt-8">
<span class="w-250px fw-bold fs-5">
<TL>Assigned tickets</TL>
</span>
</div>
}
@foreach (var ticket in AssignedTickets)
{
<div class="d-flex flex-stack py-4">
<div class="d-flex align-items-center">
<div class="ms-5">
<a href="/admin/support/@(ticket.Key.Id)" class="fs-5 fw-bold text-gray-900 text-hover-primary mb-2">@(ticket.Key.IssueTopic)</a>
@if (ticket.Value != null)
{
<div class="fw-semibold text-muted">
@(ticket.Value.Content)
</div>
}
</div>
</div>
<div class="d-flex flex-column align-items-end ms-2">
@if (ticket.Value != null)
{
<span class="text-muted fs-7 mb-1">
@(Formatter.FormatAgoFromDateTime(ticket.Value.CreatedAt, SmartTranslateService))
</span>
}
</div>
</div>
if (ticket.Key != AssignedTickets.Last().Key)
{
<div class="separator"></div>
}
}
</div>
</div>
</div>
</div>
<div class="flex-lg-row-fluid ms-lg-7 ms-xl-10">
<div class="card">
<div class="card-header">
@if (AdminService.Ticket != null)
{
<div class="card-title">
<div class="d-flex justify-content-center flex-column me-3">
<span class="fs-3 fw-bold text-gray-900 me-1 mb-2 lh-1">@(AdminService.Ticket.IssueTopic)</span>
<div class="mb-0 lh-1">
<span class="fs-6 fw-bold text-muted me-2">
<TL>Status</TL>
</span>
@switch (AdminService.Ticket.Status)
{
case TicketStatus.Closed:
<span class="badge badge-danger badge-circle w-10px h-10px me-1"></span>
break;
case TicketStatus.Open:
<span class="badge badge-success badge-circle w-10px h-10px me-1"></span>
break;
case TicketStatus.Pending:
<span class="badge badge-warning badge-circle w-10px h-10px me-1"></span>
break;
case TicketStatus.WaitingForUser:
<span class="badge badge-primary badge-circle w-10px h-10px me-1"></span>
break;
}
<span class="fs-6 fw-semibold text-muted me-5">@(AdminService.Ticket.Status)</span>
<span class="fs-6 fw-bold text-muted me-2">
<TL>Priority</TL>
</span>
@switch (AdminService.Ticket.Priority)
{
case TicketPriority.Low:
<span class="badge badge-success badge-circle w-10px h-10px me-1"></span>
break;
case TicketPriority.Medium:
<span class="badge badge-primary badge-circle w-10px h-10px me-1"></span>
break;
case TicketPriority.High:
<span class="badge badge-warning badge-circle w-10px h-10px me-1"></span>
break;
case TicketPriority.Critical:
<span class="badge badge-danger badge-circle w-10px h-10px me-1"></span>
break;
}
<span class="fs-6 fw-semibold text-muted">@(AdminService.Ticket.Priority)</span>
</div>
</div>
</div>
<div class="card-toolbar">
<div class="input-group">
<div class="me-3">
@if (AdminService.Ticket!.AssignedTo == null)
{
<WButton Text="@(SmartTranslateService.Translate("Claim"))"/>
}
else
{
<WButton Text="@(SmartTranslateService.Translate("Unclaim"))"/>
}
</div>
<select @bind="Priority" class="form-select rounded-start">
@foreach (var priority in (TicketPriority[])Enum.GetValues(typeof(TicketPriority)))
{
if (Priority == priority)
{
<option value="@(priority)" selected="">@(priority)</option>
}
else
{
<option value="@(priority)">@(priority)</option>
}
}
</select>
<WButton Text="@(SmartTranslateService.Translate("Update priority"))"
CssClasses="btn-primary"
OnClick="UpdatePriority">
</WButton>
<select @bind="Status" class="form-select">
@foreach (var status in (TicketStatus[])Enum.GetValues(typeof(TicketStatus)))
{
if (Status == status)
{
<option value="@(status)" selected="">@(status)</option>
}
else
{
<option value="@(status)">@(status)</option>
}
}
</select>
<WButton Text="@(SmartTranslateService.Translate("Update status"))"
CssClasses="btn-primary"
OnClick="UpdateStatus">
</WButton>
</div>
</div>
}
else
{
<div class="card-title">
<div class="d-flex justify-content-center flex-column me-3">
<span class="fs-4 fw-bold text-gray-900 me-1 mb-2 lh-1">
</span>
</div>
</div>
}
</div>
<div class="card-body">
<div class="scroll-y me-n5 pe-5" style="max-height: 55vh; display: flex; flex-direction: column-reverse;">
@if (AdminService.Ticket == null)
{
}
else
{
<TicketMessageView Messages="Messages" ViewAsSupport="true"/>
}
</div>
</div>
@if (AdminService.Ticket != null)
{
<div class="card-footer pt-4" id="kt_chat_messenger_footer">
<div class="d-flex flex-stack">
<table class="w-100">
<tr>
<td class="align-top">
<SmartFileSelect @ref="FileSelect"></SmartFileSelect>
</td>
<td class="w-100">
<textarea @bind="MessageText" class="form-control mb-3 form-control-flush" rows="1" placeholder="@(SmartTranslateService.Translate("Type a message"))"></textarea>
</td>
<td class="align-top">
<WButton Text="@(SmartTranslateService.Translate("Send"))"
WorkingText="@(SmartTranslateService.Translate("Sending"))"
CssClasses="btn-primary ms-2"
OnClick="SendMessage">
</WButton>
</td>
</tr>
</table>
</div>
</div>
}
</div>
</div>
</div>
@code
{
[Parameter]
public int Id { get; set; }
private Dictionary<Ticket, TicketMessage?> AssignedTickets;
private Dictionary<Ticket, TicketMessage?> UnAssignedTickets;
private List<TicketMessage> Messages = new();
private string MessageText;
private SmartFileSelect FileSelect;
private TicketPriority Priority;
private TicketStatus Status;
protected override async Task OnParametersSetAsync()
{
await Unsubscribe();
await ReloadTickets();
await Subscribe();
await InvokeAsync(StateHasChanged);
}
private async Task UpdatePriority()
{
await AdminService.UpdatePriority(Priority);
}
private async Task UpdateStatus()
{
await AdminService.UpdateStatus(Status);
}
private async Task SendMessage()
{
if (string.IsNullOrEmpty(MessageText) && FileSelect.SelectedFile != null)
MessageText = "File upload";
if (string.IsNullOrEmpty(MessageText))
return;
var msg = await AdminService.Send(MessageText, FileSelect.SelectedFile);
Messages.Add(msg);
MessageText = "";
FileSelect.SelectedFile = null;
await InvokeAsync(StateHasChanged);
}
private async Task Subscribe()
{
await EventSystem.On<Ticket>("tickets.new", this, async _ =>
{
await ReloadTickets(false);
await InvokeAsync(StateHasChanged);
});
if (AdminService.Ticket != null)
{
await EventSystem.On<TicketMessage>($"tickets.{AdminService.Ticket.Id}.message", this, async message =>
{
if (message.Sender != null && message.Sender.Id == IdentityService.User.Id && message.IsSupportMessage)
return;
Messages.Add(message);
await InvokeAsync(StateHasChanged);
});
await EventSystem.On<Ticket>($"tickets.{AdminService.Ticket.Id}.status", this, async _ =>
{
await ReloadTickets(false);
await InvokeAsync(StateHasChanged);
});
}
}
private async Task Unsubscribe()
{
await EventSystem.Off("tickets.new", this);
if (AdminService.Ticket != null)
{
await EventSystem.Off($"tickets.{AdminService.Ticket.Id}.message", this);
await EventSystem.Off($"tickets.{AdminService.Ticket.Id}.status", this);
}
}
private async Task ReloadTickets(bool reloadMessages = true)
{
AdminService.Ticket = null;
//AssignedTickets = await AdminService.GetAssigned();
//UnAssignedTickets = await AdminService.GetUnAssigned();
if (Id != 0)
{
AdminService.Ticket = AssignedTickets
.FirstOrDefault(x => x.Key.Id == Id)
.Key ?? null;
if (AdminService.Ticket == null)
{
AdminService.Ticket = UnAssignedTickets
.FirstOrDefault(x => x.Key.Id == Id)
.Key ?? null;
}
if (AdminService.Ticket == null)
return;
Status = AdminService.Ticket.Status;
Priority = AdminService.Ticket.Priority;
if (reloadMessages)
{
var msgs = await AdminService.GetMessages();
Messages = msgs.ToList();
}
}
}
public async void Dispose()
{
await Unsubscribe();
}
}

View File

@@ -1,293 +0,0 @@
@page "/admin/support/view/{Id:int}"
@using Moonlight.App.Services.Tickets
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Misc
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Events
@using Moonlight.App.Services.Sessions
@using Moonlight.Shared.Components.Tickets
@inject TicketAdminService TicketAdminService
@inject SmartTranslateService SmartTranslateService
@inject Repository<Ticket> TicketRepository
@inject EventSystem Event
@inject IdentityService IdentityService
@implements IDisposable
@attribute [PermissionRequired(nameof(Permissions.AdminSupportView))]
<LazyLoader @ref="LazyLoader" Load="Load">
@if (Ticket == null)
{
<NotFoundAlert/>
}
else
{
<div class="card">
<div class="row g-0">
<div class="col-xl-9 col-lg-8">
<div class="card-body border-end">
<div class="row mb-4 pb-2 g-3">
<span class="fs-2 fw-bold">@(Ticket.IssueTopic)</span>
</div>
<span class="fs-4">
<TL>Issue description</TL>:
</span>
<p class="fs-5 text-muted">
@(Formatter.FormatLineBreaks(Ticket.IssueDescription))
</p>
<span class="fs-4">
<TL>Issue resolve tries</TL>:
</span>
<p class="fs-5 text-muted">
@(Formatter.FormatLineBreaks(Ticket.IssueTries))
</p>
</div>
<div class="card-body border-end border-top bg-black">
<TicketMessageView Messages="Messages" ViewAsSupport="true"/>
</div>
<div class="card-footer pt-4">
<div class="d-flex flex-stack">
<table class="w-100">
<tr>
<td class="align-top">
<SmartFileSelect @ref="FileSelect"></SmartFileSelect>
</td>
<td class="w-100">
<textarea @bind="MessageContent" class="form-control mb-3 form-control-flush" rows="1" placeholder="@(SmartTranslateService.Translate("Type a message"))"></textarea>
</td>
<td class="align-top">
<WButton Text="@(SmartTranslateService.Translate("Send"))"
WorkingText="@(SmartTranslateService.Translate("Sending"))"
CssClasses="btn-primary ms-2"
OnClick="SendMessage"/>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="col-xl-3 col-lg-4">
<div class="card-header">
<h6 class="card-title mb-0">
<TL>Ticket details</TL>
</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-borderless align-middle mb-0">
<tbody>
<tr>
<th>
<TL>Ticket ID</TL>
</th>
<td>@(Ticket.Id)</td>
</tr>
<tr>
<th>
<TL>User</TL>
</th>
<td>
<a href="/admin/users/view/@(Ticket.CreatedBy.Id)">
@(Ticket.CreatedBy.FirstName) @(Ticket.CreatedBy.LastName)
</a>
</td>
</tr>
<tr>
<th>
<TL>Subject</TL>
</th>
<td>
<TL>@(Ticket.Subject)</TL>
</td>
</tr>
<tr>
<th>
<TL>Subject ID</TL>
</th>
<td>@(Ticket.SubjectId)</td>
</tr>
<tr>
<th>
<TL>Assigned to</TL>
</th>
@if (Ticket.AssignedTo == null)
{
<td>
<TL>None</TL>
</td>
}
else
{
<td>@(Ticket.AssignedTo.FirstName) @(Ticket.AssignedTo.LastName)</td>
}
</tr>
<tr>
<th>
<TL>Status</TL>
</th>
<td>
<select @bind="StatusModified" class="form-select">
@foreach (var status in (TicketStatus[])Enum.GetValues(typeof(TicketStatus)))
{
if (StatusModified == status)
{
<option value="@(status)" selected="">@(status)</option>
}
else
{
<option value="@(status)">@(status)</option>
}
}
</select>
</td>
</tr>
<tr>
<th>
<TL>Priority</TL>
</th>
<td>
<select @bind="PriorityModified" class="form-select">
@foreach (var priority in (TicketPriority[])Enum.GetValues(typeof(TicketPriority)))
{
if (PriorityModified == priority)
{
<option value="@(priority)" selected="">@(priority)</option>
}
else
{
<option value="@(priority)">@(priority)</option>
}
}
</select>
</td>
</tr>
<tr>
<th>
<TL>Created at</TL>
</th>
<td>@(Formatter.FormatDate(Ticket.CreatedAt))</td>
</tr>
<tr>
<th></th>
<td>
<WButton Text="@(SmartTranslateService.Translate("Save"))" OnClick="Save"/>
</td>
</tr>
<tr>
<th>
<WButton Text="@(SmartTranslateService.Translate("Claim"))" OnClick="() => SetClaim(IdentityService.User)"/>
</th>
<td>
<WButton Text="@(SmartTranslateService.Translate("Unclaim"))" OnClick="() => SetClaim(null)"/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
}
</LazyLoader>
@code
{
[Parameter]
public int Id { get; set; }
private Ticket? Ticket { get; set; }
private TicketPriority PriorityModified;
private TicketStatus StatusModified;
private List<TicketMessage> Messages = new();
private SmartFileSelect FileSelect;
private string MessageContent = "";
private LazyLoader LazyLoader;
private async Task Load(LazyLoader _)
{
Ticket = TicketRepository
.Get()
.Include(x => x.AssignedTo)
.Include(x => x.CreatedBy)
.FirstOrDefault(x => x.Id == Id);
if (Ticket != null)
{
TicketAdminService.Ticket = Ticket;
PriorityModified = Ticket.Priority;
StatusModified = Ticket.Status;
Messages = (await TicketAdminService.GetMessages()).ToList();
// Register events
await Event.On<TicketMessage>($"tickets.{Ticket.Id}.message", this, async message =>
{
if (message.Sender != null && message.Sender.Id == IdentityService.User.Id && message.IsSupportMessage)
return;
Messages.Add(message);
await InvokeAsync(StateHasChanged);
});
await Event.On<Ticket>($"tickets.{Ticket.Id}.status", this, async _ =>
{
//TODO: Does not work because of data caching. So we dont reload because it will look the same anyways
//await LazyLoader.Reload();
});
}
}
private async Task Save()
{
if (PriorityModified != Ticket!.Priority)
await TicketAdminService.UpdatePriority(PriorityModified);
if (StatusModified != Ticket!.Status)
await TicketAdminService.UpdateStatus(StatusModified);
}
private async Task SetClaim(User? user)
{
await TicketAdminService.SetClaim(user);
}
private async Task SendMessage()
{
if (string.IsNullOrEmpty(MessageContent) && FileSelect.SelectedFile != null)
MessageContent = "File upload";
if (string.IsNullOrEmpty(MessageContent))
return;
var msg = await TicketAdminService.Send(
MessageContent,
FileSelect.SelectedFile
);
Messages.Add(msg);
MessageContent = "";
FileSelect.SelectedFile = null;
await InvokeAsync(StateHasChanged);
}
public async void Dispose()
{
if (Ticket != null)
{
await Event.Off($"tickets.{Ticket.Id}.message", this);
await Event.Off($"tickets.{Ticket.Id}.status", this);
}
}
}

View File

@@ -1,53 +0,0 @@
@page "/admin/system/configuration"
@using Moonlight.App.Services
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Configuration
@using Moonlight.App.Services.Interop
@inject ConfigService ConfigService
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService
@attribute [PermissionRequired(nameof(Permissions.AdminSysConfiguration))]
<AdminSystemNavigation Index="8"/>
<LazyLoader Load="Load">
<div class="card">
<SmartForm Model="Config" OnValidSubmit="OnSubmit">
<div class="card-body">
<SmartFormClass Model="Config"/>
</div>
<div class="card-footer">
<div class="text-end">
<button type="submit" class="btn btn-success">
<TL>Save</TL>
</button>
</div>
</div>
</SmartForm>
</div>
</LazyLoader>
@code
{
private ConfigV1 Config;
private Task Load(LazyLoader lazyLoader)
{
Config = ConfigService.Get();
return Task.CompletedTask;
}
private async Task OnSubmit()
{
ConfigService.Save(Config);
await ToastService.Success(
SmartTranslateService.Translate(
"Successfully saved and reloaded configuration. Some changes may take affect after a restart of moonlight"
)
);
}
}

Some files were not shown because too many files have changed in this diff Show More