Upgraded users, sessions and settings page
This commit is contained in:
@@ -78,9 +78,9 @@
|
|||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="card card-body m-15 p-15">
|
<div class="card card-body m-15 p-15">
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load"
|
||||||
<span></span>
|
UseDefaultValues="false"
|
||||||
</LazyLoader>
|
TimeUntilSpinnerIsShown="TimeSpan.Zero" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
@page "/admin/sys/settings"
|
@page "/admin/sys/settings"
|
||||||
|
|
||||||
|
@using System.ComponentModel
|
||||||
|
@using System.Linq.Expressions
|
||||||
@using Moonlight.Core.UI.Components.Navigations
|
@using Moonlight.Core.UI.Components.Navigations
|
||||||
@using System.Reflection
|
@using System.Reflection
|
||||||
|
@using MoonCore.Blazor.Models.Fast
|
||||||
|
@using MoonCore.Helpers
|
||||||
@using MoonCore.Services
|
@using MoonCore.Services
|
||||||
@using Moonlight.Core.Configuration
|
@using Moonlight.Core.Configuration
|
||||||
|
|
||||||
@@ -10,9 +14,9 @@
|
|||||||
|
|
||||||
@attribute [RequirePermission(9999)]
|
@attribute [RequirePermission(9999)]
|
||||||
|
|
||||||
<AdminSysNavigation Index="1" />
|
<AdminSysNavigation Index="1"/>
|
||||||
|
|
||||||
@if (ModelToShow == null)
|
@if (CurrentModel == null)
|
||||||
{
|
{
|
||||||
<IconAlert Title="No resource to show" Icon="bx-x">
|
<IconAlert Title="No resource to show" Icon="bx-x">
|
||||||
No model found to show. Please refresh the page to go back
|
No model found to show. Please refresh the page to go back
|
||||||
@@ -20,20 +24,36 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<div class="mt-5">
|
||||||
|
<Tooltip>
|
||||||
|
Changes to these settings are live applied. The save button only make the changes persistently saved to disk
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card mt-5 mb-5">
|
<div class="card mt-5 mb-5">
|
||||||
<div class="card-header border-bottom-0">
|
<div class="card-header border-bottom-0">
|
||||||
@{
|
<h3 class="card-title">
|
||||||
string title;
|
@if (Path.Length == 0)
|
||||||
|
{
|
||||||
if (Path.Length == 0)
|
<span>Configuration</span>
|
||||||
title = "Configuration";
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
title = "Configuration - " + string.Join(" - ", Path);
|
<span class="text-muted">
|
||||||
|
<span class="align-middle">Configuration</span>
|
||||||
|
|
||||||
|
@foreach (var subPart in Path.SkipLast(1))
|
||||||
|
{
|
||||||
|
<i class="bx bx-sm bx-chevron-right me-1 align-middle"></i>
|
||||||
|
<span class="align-middle">@subPart</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<i class="bx bx-sm bx-chevron-right align-middle text-muted"></i>
|
||||||
|
<span class="align-middle">@Path.Last()</span>
|
||||||
|
</span>
|
||||||
}
|
}
|
||||||
}
|
</h3>
|
||||||
|
|
||||||
<h3 class="card-title">@(title)</h3>
|
|
||||||
<div class="card-toolbar">
|
<div class="card-toolbar">
|
||||||
<WButton OnClick="Reload" CssClasses="btn btn-icon btn-warning me-3">
|
<WButton OnClick="Reload" CssClasses="btn btn-icon btn-warning me-3">
|
||||||
<i class="bx bx-sm bx-revision"></i>
|
<i class="bx bx-sm bx-revision"></i>
|
||||||
@@ -44,34 +64,24 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tooltip>
|
|
||||||
Changes to these settings are live applied. The save button only make the changes persistently saved to disk
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<div class="row mt-5">
|
<div class="row mt-5">
|
||||||
<div class="col-md-3 col-12 mb-5">
|
<div class="col-md-3 col-12 mb-5">
|
||||||
<div class="card card-body">
|
<div class="card card-body">
|
||||||
@{
|
@foreach (var item in SidebarItems)
|
||||||
var props = ModelToShow
|
|
||||||
.GetType()
|
|
||||||
.GetProperties()
|
|
||||||
.Where(x => x.PropertyType.Assembly.FullName!.Contains("Moonlight") && x.PropertyType.IsClass)
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
@foreach (var prop in props)
|
|
||||||
{
|
{
|
||||||
<div class="d-flex flex-stack">
|
<div class="d-flex flex-stack">
|
||||||
<div class="d-flex align-items-center flex-row-fluid flex-wrap">
|
<div class="d-flex align-items-center flex-row-fluid flex-wrap">
|
||||||
<a href="/admin/sys/settings?section=@(Section + "/" + prop.Name)" class="fs-4 text-primary">@(prop.Name)</a>
|
<a href="/admin/sys/settings?section=@(Section + "/" + item)" class="fs-4 text-primary">
|
||||||
|
@item
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (Path.Length != 0)
|
@if (Path.Length != 0)
|
||||||
{
|
{
|
||||||
<div class="d-flex flex-stack @(props.Length != 0 ? "mt-5" : "")">
|
<div class="d-flex flex-stack @(SidebarItems.Length != 0 ? "mt-5" : "")">
|
||||||
<div class="d-flex align-items-center flex-row-fluid flex-wrap">
|
<div class="d-flex align-items-center flex-row-fluid flex-wrap">
|
||||||
<a href="/admin/sys/@(GetBackPath())" class="fs-4 text-primary">Back</a>
|
<a href="/admin/sys/@(GetBackPath())" class="fs-4 text-primary">Back</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,42 +90,21 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-9 col-12">
|
<div class="col-md-9 col-12">
|
||||||
<div class="card card-body">
|
<LazyLoader @ref="LazyLoader" Load="Load" UseDefaultValues="false" TimeUntilSpinnerIsShown="TimeSpan.Zero">
|
||||||
<div class="row g-5">
|
<FastForm Model="CurrentModel" OnConfigure="OnFormConfigure"/>
|
||||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
</LazyLoader>
|
||||||
<AutoFormGenerator Model="ModelToShow" />
|
|
||||||
@*
|
|
||||||
@foreach (var prop in Properties)
|
|
||||||
{
|
|
||||||
<div class="col-md-6 col-12">
|
|
||||||
@{
|
|
||||||
var typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
|
|
||||||
var rf = ComponentHelper.FromType(typeToCreate, parameters =>
|
|
||||||
{
|
|
||||||
parameters.Add("Data", ModelToShow);
|
|
||||||
parameters.Add("Property", prop);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@rf
|
|
||||||
</div>
|
|
||||||
}*@
|
|
||||||
</LazyLoader>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter]
|
[Parameter] [SupplyParameterFromQuery] public string? Section { get; set; } = "";
|
||||||
[SupplyParameterFromQuery]
|
|
||||||
public string? Section { get; set; } = "";
|
private object? CurrentModel;
|
||||||
|
private string[] SidebarItems = [];
|
||||||
private object? ModelToShow;
|
private string[] Path = [];
|
||||||
private PropertyInfo[] Properties = Array.Empty<PropertyInfo>();
|
private PropertyInfo[] Properties = [];
|
||||||
private string[] Path = Array.Empty<string>();
|
|
||||||
|
|
||||||
private LazyLoader? LazyLoader;
|
private LazyLoader? LazyLoader;
|
||||||
|
|
||||||
@@ -124,21 +113,36 @@ else
|
|||||||
if (Section != null && Section.StartsWith("/"))
|
if (Section != null && Section.StartsWith("/"))
|
||||||
Section = Section.TrimStart('/');
|
Section = Section.TrimStart('/');
|
||||||
|
|
||||||
Path = Section != null ? Section.Split("/") : Array.Empty<string>();
|
Path = Section != null ? Section.Split("/") : [];
|
||||||
|
|
||||||
ModelToShow = Resolve(ConfigService.Get(), Path, 0);
|
CurrentModel = Resolve(ConfigService.Get(), Path, 0);
|
||||||
|
|
||||||
if (ModelToShow != null)
|
if (CurrentModel == null)
|
||||||
{
|
{
|
||||||
Properties = ModelToShow
|
SidebarItems = [];
|
||||||
.GetType()
|
Properties = [];
|
||||||
.GetProperties()
|
|
||||||
.Where(x => !x.PropertyType.Assembly.FullName!.Contains("Moonlight"))
|
|
||||||
.ToArray();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Properties = Array.Empty<PropertyInfo>();
|
var props = CurrentModel
|
||||||
|
.GetType()
|
||||||
|
.GetProperties()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
SidebarItems = props
|
||||||
|
.Where(x =>
|
||||||
|
x.PropertyType.IsClass &&
|
||||||
|
x.PropertyType.Namespace!.StartsWith("Moonlight")
|
||||||
|
)
|
||||||
|
.Select(x => x.Name)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
Properties = props
|
||||||
|
.Where(x =>
|
||||||
|
!x.PropertyType.Namespace.StartsWith("Moonlight") &&
|
||||||
|
DefaultComponentSelector.GetDefault(x.PropertyType) != null // Check if a component has been registered for that type
|
||||||
|
)
|
||||||
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
@@ -146,16 +150,40 @@ else
|
|||||||
if (LazyLoader != null)
|
if (LazyLoader != null)
|
||||||
await LazyLoader.Reload();
|
await LazyLoader.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnFormConfigure(FastConfiguration<object> configuration)
|
||||||
|
{
|
||||||
|
if(CurrentModel == null) // This will technically never be true because of the ui logic
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var property in Properties)
|
||||||
|
{
|
||||||
|
var propConfig = configuration
|
||||||
|
.AddProperty(CreatePropertyAccessExpression(property))
|
||||||
|
.WithDefaultComponent();
|
||||||
|
|
||||||
|
var customAttributes = property.GetCustomAttributes(false);
|
||||||
|
|
||||||
|
if(customAttributes.Length == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (TryGetAttribute(customAttributes, out DisplayNameAttribute nameAttribute))
|
||||||
|
propConfig.WithName(nameAttribute.DisplayName);
|
||||||
|
else
|
||||||
|
propConfig.WithName(Formatter.ConvertCamelCaseToSpaces(property.Name));
|
||||||
|
|
||||||
|
if (TryGetAttribute(customAttributes, out DescriptionAttribute descriptionAttribute))
|
||||||
|
propConfig.WithDescription(descriptionAttribute.Description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string GetBackPath()
|
private string GetBackPath()
|
||||||
{
|
{
|
||||||
if (Path.Length == 1)
|
if (Path.Length == 1)
|
||||||
return "settings";
|
return "settings";
|
||||||
else
|
|
||||||
{
|
var path = string.Join('/', Path.Take(Path.Length - 1)).TrimEnd('/');
|
||||||
var path = string.Join('/', Path.Take(Path.Length - 1)).TrimEnd('/');
|
return $"settings?section={path}";
|
||||||
return $"settings?section={path}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private object? Resolve(object model, string[] path, int index)
|
private object? Resolve(object model, string[] path, int index)
|
||||||
@@ -177,20 +205,60 @@ else
|
|||||||
return Resolve(prop.GetValue(model)!, path, index + 1);
|
return Resolve(prop.GetValue(model)!, path, index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task Load(LazyLoader arg)
|
private Task Load(LazyLoader _) => Task.CompletedTask; // Seems useless, it more or less is, but it shows a nice loading ui while the form changes
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Save()
|
private async Task Save() // Saves all changes to disk, all changes are live updated as the config service reference will be edited directly
|
||||||
{
|
{
|
||||||
ConfigService.Save();
|
ConfigService.Save();
|
||||||
await ToastService.Success("Successfully saved config to disk");
|
await ToastService.Success("Successfully saved config to disk");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Reload()
|
private async Task Reload() // This will also discard all unsaved changes
|
||||||
{
|
{
|
||||||
ConfigService.Reload();
|
ConfigService.Reload();
|
||||||
await ToastService.Info("Reloaded configuration from disk");
|
await ToastService.Info("Reloaded configuration from disk");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Building lambda expressions at runtime using reflection is nice ;3
|
||||||
|
public static Expression<Func<object, object?>> CreatePropertyAccessExpression(PropertyInfo property)
|
||||||
|
{
|
||||||
|
// Get the type that declares the property
|
||||||
|
Type declaringType = property.DeclaringType!;
|
||||||
|
|
||||||
|
// Create a parameter expression for the input object
|
||||||
|
ParameterExpression param = Expression.Parameter(typeof(object), "obj");
|
||||||
|
|
||||||
|
// Create an expression to cast the input object to the declaring type
|
||||||
|
UnaryExpression cast = Expression.Convert(param, declaringType);
|
||||||
|
|
||||||
|
// Create an expression to access the property
|
||||||
|
MemberExpression propertyAccess = Expression.Property(cast, property);
|
||||||
|
|
||||||
|
// Create an expression to cast the property value to object
|
||||||
|
UnaryExpression castResult = Expression.Convert(propertyAccess, typeof(object));
|
||||||
|
|
||||||
|
// Create the final lambda expression
|
||||||
|
Expression<Func<object, object?>> lambda = Expression.Lambda<Func<object, object?>>(
|
||||||
|
castResult, param);
|
||||||
|
|
||||||
|
return lambda;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From MoonCore. TODO: Maybe provide this and the above function as mooncore helper
|
||||||
|
private bool TryGetAttribute<T>(object[] attributes, out T result) where T : Attribute
|
||||||
|
{
|
||||||
|
var searchType = typeof(T);
|
||||||
|
|
||||||
|
var attr = attributes
|
||||||
|
.FirstOrDefault(x => x.GetType() == searchType);
|
||||||
|
|
||||||
|
if (attr == null)
|
||||||
|
{
|
||||||
|
result = default!;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = (attr as T)!;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
@page "/admin/users"
|
@page "/admin/users"
|
||||||
|
|
||||||
|
@using System.ComponentModel.DataAnnotations
|
||||||
@using Moonlight.Core.UI.Components.Navigations
|
@using Moonlight.Core.UI.Components.Navigations
|
||||||
@using MoonCore.Abstractions
|
@using MoonCore.Abstractions
|
||||||
|
@using MoonCore.Blazor.Forms.Fast.Components
|
||||||
|
@using MoonCore.Blazor.Models.Fast
|
||||||
|
@using MoonCore.Blazor.Models.Fast.Validators
|
||||||
@using Moonlight.Core.Database.Entities
|
@using Moonlight.Core.Database.Entities
|
||||||
@using MoonCore.Exceptions
|
@using MoonCore.Exceptions
|
||||||
@using Moonlight.Core.Models.Abstractions
|
@using Moonlight.Core.Models.Abstractions
|
||||||
@@ -14,36 +18,30 @@
|
|||||||
|
|
||||||
<AdminUsersNavigation Index="0"/>
|
<AdminUsersNavigation Index="0"/>
|
||||||
|
|
||||||
<AutoCrud TItem="User"
|
<FastCrud TItem="User"
|
||||||
TCreateForm="CreateUserForm"
|
Search="Search"
|
||||||
TUpdateForm="UpdateUserForm"
|
OnConfigure="OnConfigure"
|
||||||
Loader="Load"
|
OnConfigureCreate="OnConfigureCreate"
|
||||||
CustomAdd="Add"
|
OnConfigureEdit="OnConfigureEdit">
|
||||||
ValidateUpdate="ValidateUpdate">
|
|
||||||
<View>
|
<View>
|
||||||
<MCBColumn TItem="User" Field="@(x => x.Id)" Title="Id" Filterable="true"/>
|
<MCBColumn TItem="User" Field="@(x => x.Id)" Title="Id" Filterable="true"/>
|
||||||
<MCBColumn TItem="User" Field="@(x => x.Email)" Title="Email" Filterable="true"/>
|
<MCBColumn TItem="User" Field="@(x => x.Email)" Title="Email" Filterable="true"/>
|
||||||
<MCBColumn TItem="User" Field="@(x => x.Username)" Title="Username" Filterable="true"/>
|
<MCBColumn TItem="User" Field="@(x => x.Username)" Title="Username" Filterable="true"/>
|
||||||
<MCBColumn TItem="User" Field="@(x => x.CreatedAt)" Title="Created at"/>
|
<MCBColumn TItem="User" Field="@(x => x.CreatedAt)" Title="Created at"/>
|
||||||
</View>@*
|
</View>
|
||||||
<UpdateActions>
|
<EditToolbar>
|
||||||
<WButton OnClick="() => ChangePassword(context)" CssClasses="btn btn-info me-2">
|
<WButton OnClick="() => ChangePassword(context)" CssClasses="btn btn-info me-2">
|
||||||
<i class="bx bx-sm bxs-key"></i>
|
<i class="bx bx-sm bxs-key"></i>
|
||||||
Change password
|
Change password
|
||||||
</WButton>
|
</WButton>
|
||||||
</UpdateActions>*@
|
</EditToolbar>
|
||||||
</AutoCrud>
|
</FastCrud>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private IEnumerable<User> Load(Repository<User> repository)
|
|
||||||
{
|
|
||||||
return repository.Get();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ChangePassword(User user)
|
private async Task ChangePassword(User user)
|
||||||
{
|
{
|
||||||
await AlertService.Text("", "Enter a new password for {user.Username}", async newPassword =>
|
await AlertService.Text($"Change password for '{user.Username}'", "Enter a new password for {user.Username}", async newPassword =>
|
||||||
{
|
{
|
||||||
// This handles empty and canceled input
|
// This handles empty and canceled input
|
||||||
if (string.IsNullOrEmpty(newPassword))
|
if (string.IsNullOrEmpty(newPassword))
|
||||||
@@ -52,18 +50,66 @@
|
|||||||
await AuthenticationProvider.ChangePassword(user, newPassword);
|
await AuthenticationProvider.ChangePassword(user, newPassword);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Add(User user)
|
private void OnConfigure(FastCrudConfiguration<User> configuration)
|
||||||
{
|
{
|
||||||
var result = await AuthenticationProvider.Register(user.Username, user.Email, user.Password);
|
configuration.CustomCreate += async user =>
|
||||||
|
{
|
||||||
|
var result = await AuthenticationProvider.Register(user.Username, user.Email, user.Password);
|
||||||
|
|
||||||
if (result == null)
|
if (result == null)
|
||||||
throw new DisplayException("An unknown error occured while creating user");
|
throw new DisplayException("An unknown error occured while creating user");
|
||||||
|
};
|
||||||
|
|
||||||
|
configuration.ValidateEdit += async user =>
|
||||||
|
{
|
||||||
|
await AuthenticationProvider.ChangeDetails(user, user.Email, user.Username);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// To notify the authentication provider before we update the data in the database, we call it here
|
private void OnConfigureCreate(FastConfiguration<User> configuration)
|
||||||
private async Task ValidateUpdate(User user)
|
|
||||||
{
|
{
|
||||||
await AuthenticationProvider.ChangeDetails(user, user.Email, user.Username);
|
configuration.AddProperty(x => x.Username)
|
||||||
|
.WithDefaultComponent()
|
||||||
|
.WithValidation(FastValidators.Required)
|
||||||
|
.WithValidation(RegexValidator.Create("^[a-z][a-z0-9]*$", "Usernames can only contain lowercase characters and numbers and should not start with a number"))
|
||||||
|
.WithValidation<string>(x => x.Length >= 6 ? ValidationResult.Success : new ValidationResult("The username is too short"))
|
||||||
|
.WithValidation<string>(x => x.Length <= 20 ? ValidationResult.Success : new ValidationResult("The username cannot be longer than 20 characters"));
|
||||||
|
|
||||||
|
configuration.AddProperty(x => x.Email)
|
||||||
|
.WithDefaultComponent()
|
||||||
|
.WithValidation(FastValidators.Required)
|
||||||
|
.WithValidation(RegexValidator.Create("^.+@.+$", "You need to enter a valid email address"));
|
||||||
|
|
||||||
|
configuration.AddProperty(x => x.Password)
|
||||||
|
.WithDefaultComponent()
|
||||||
|
.WithValidation(FastValidators.Required)
|
||||||
|
.WithValidation<string>(x => x.Length >= 8 ? ValidationResult.Success : new ValidationResult("The password must be at least 8 characters long"))
|
||||||
|
.WithValidation<string>(x => x.Length <= 256 ? ValidationResult.Success : new ValidationResult("The password must not be longer than 256 characters"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigureEdit(FastConfiguration<User> configuration, User currentUser)
|
||||||
|
{
|
||||||
|
configuration.AddProperty(x => x.Username)
|
||||||
|
.WithDefaultComponent()
|
||||||
|
.WithValidation(FastValidators.Required)
|
||||||
|
.WithValidation(RegexValidator.Create("^[a-z][a-z0-9]*$", "Usernames can only contain lowercase characters and numbers and should not start with a number"))
|
||||||
|
.WithValidation<string>(x => x.Length >= 6 ? ValidationResult.Success : new ValidationResult("The username is too short"))
|
||||||
|
.WithValidation<string>(x => x.Length <= 20 ? ValidationResult.Success : new ValidationResult("The username cannot be longer than 20 characters"));
|
||||||
|
|
||||||
|
configuration.AddProperty(x => x.Email)
|
||||||
|
.WithDefaultComponent()
|
||||||
|
.WithValidation(FastValidators.Required)
|
||||||
|
.WithValidation(RegexValidator.Create("^.+@.+$", "You need to enter a valid email address"));
|
||||||
|
|
||||||
|
configuration.AddProperty(x => x.Totp)
|
||||||
|
.WithComponent<bool, SwitchComponent>()
|
||||||
|
.WithName("Two factor authentication")
|
||||||
|
.WithDescription("This toggles the use of the two factor authentication");
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<User> Search(IEnumerable<User> source, string term)
|
||||||
|
{
|
||||||
|
return source.Where(x => x.Username.Contains(term) || x.Email.Contains(term));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,8 +62,8 @@
|
|||||||
<Template>
|
<Template>
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<div class="btn btn-group">
|
<div class="btn btn-group">
|
||||||
<WButton OnClick="() => Message(context)" Text="Message" CssClasses="btn btn-primary"/>
|
<WButton OnClick="() => Message(context)" CssClasses="btn btn-primary">Message</WButton>
|
||||||
<WButton OnClick="() => Redirect(context)" Text="Redirect" CssClasses="btn btn-warning"/>
|
<WButton OnClick="() => Redirect(context)" CssClasses="btn btn-warning">Redirect</WButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Template>
|
</Template>
|
||||||
|
|||||||
@@ -93,8 +93,8 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="MoonCore" Version="1.3.8" />
|
<PackageReference Include="MoonCore" Version="1.4.0" />
|
||||||
<PackageReference Include="MoonCore.Blazor" Version="1.0.3" />
|
<PackageReference Include="MoonCore.Blazor" Version="1.0.8" />
|
||||||
<PackageReference Include="Otp.NET" Version="1.3.0" />
|
<PackageReference Include="Otp.NET" Version="1.3.0" />
|
||||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
@using MoonCore.Blazor.Helpers
|
@using MoonCore.Blazor.Helpers
|
||||||
@using MoonCore.Blazor.Forms.Table
|
@using MoonCore.Blazor.Forms.Table
|
||||||
@using MoonCore.Blazor.Forms.Auto
|
@using MoonCore.Blazor.Forms.Auto
|
||||||
|
@using MoonCore.Blazor.Forms.Fast
|
||||||
|
|
||||||
@using Moonlight.Core.UI
|
@using Moonlight.Core.UI
|
||||||
@using Moonlight.Core.Attributes
|
@using Moonlight.Core.Attributes
|
||||||
|
|||||||
Reference in New Issue
Block a user