Merge branch 'abpframework:dev' into dev

pull/17129/head
Masood Khoshgard 2 years ago committed by GitHub
commit 81ea8f248c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,8 +4,8 @@ ABP is a **modular application framework** which consists of dozens of **NuGet &
There are **two types of modules.** They don't have any structural difference but are categorized by functionality and purpose:
* [**Framework modules**](https://github.com/abpframework/abp/tree/master/framework/src): These are **core modules of the framework** like caching, emailing, theming, security, serialization, validation, EF Core integration, MongoDB integration... etc. They do not have application/business functionalities but makes your daily development easier by providing common infrastructure, integration and abstractions.
* [**Application modules**](https://github.com/abpframework/abp/tree/master/modules): These modules implement specific application/business functionalities like blogging, document management, identity management, tenant management... etc. They generally have their own entities, services, APIs and UI components.
* [**Framework modules**](https://github.com/abpframework/abp/tree/dev/framework/src): These are **core modules of the framework** like caching, emailing, theming, security, serialization, validation, EF Core integration, MongoDB integration... etc. They do not have application/business functionalities but makes your daily development easier by providing common infrastructure, integration and abstractions.
* [**Application modules**](https://github.com/abpframework/abp/tree/dev/modules): These modules implement specific application/business functionalities like blogging, document management, identity management, tenant management... etc. They generally have their own entities, services, APIs and UI components.
## Open Source Application Modules

@ -177,7 +177,8 @@ public class BookStoreSettingPageContributor : ISettingPageContributor
new SettingPageGroup(
"Volo.Abp.MySettingGroup",
"MySettingGroup",
typeof(MySettingGroupViewComponent)
typeof(MySettingGroupViewComponent),
order : 1
)
);
@ -240,7 +241,8 @@ public class BookStoreSettingComponentContributor : ISettingComponentContributor
new SettingComponentGroup(
"Volo.Abp.MySettingGroup",
"MySettingGroup",
typeof(MySettingGroupComponent)
typeof(MySettingGroupComponent),
order : 1
)
);

@ -48,6 +48,33 @@ The Features action opens a modal to enable/disable/set [features](../Features.m
*Manage Host features* button is used to set features for the host side, if you use the features of your application also in the host side.
## Distributed Events
This module defines the following ETOs (Event Transfer Objects) to allow you to subscribe to changes on the entities of the module;
- `TenantEto` is published on changes done on an `Tenant` entity.
**Example: Get notified when a new tenant has been created**
```
public class MyHandler :
IDistributedEventHandler<EntityCreatedEto<TenantEto>>,
ITransientDependency
{
public async Task HandleEventAsync(EntityCreatedEto<TenantEto> eventData)
{
TenantEto tenant = eventData.Entity;
// TODO: ...
}
}
```
`TenantEto` is configured to automatically publish the events. You should configure yourself for the others. See the [Distributed Event Bus document](https://github.com/abpframework/abp/blob/rel-7.3/docs/en/Distributed-Event-Bus.md) to learn details of the pre-defined events.
> Subscribing to the distributed events is especially useful for distributed scenarios (like microservice architecture). If you are building a monolithic application, or listening events in the same process that runs the Tenant Management Module, then subscribing to the [local events](https://github.com/abpframework/abp/blob/rel-7.3/docs/en/Local-Event-Bus.md) can be more efficient and easier.
## Internals
This section can be used as a reference if you want to [customize](../Customizing-Application-Modules-Guide.md) this module without changing [its source code](https://github.com/abpframework/abp/tree/dev/modules/tenant-management).

@ -0,0 +1,80 @@
# Caps Lock Directive
In password inputs, You may want to show if Caps Lock is on. To make this even easier, you can use the `TrackCapsLockDirective` which has been exposed by the `@abp/ng.core` package.
## Getting Started
`TrackCapsLockDirective` is standalone. In order to use the `TrackCapsLockDirective` in an HTML template, import it to related module or your standalone component:
**Importing to NgModule**
```ts
import { TrackCapsLockDirective } from '@abp/ng.core';
@NgModule({
//...
declarations: [
...,
TestComponent
],
imports: [
...,
TrackCapsLockDirective
],
})
export class MyFeatureModule {}
```
## Usage
The `TrackCapsLockDirective` is very easy to use. The directive's selector is **`abpCapsLock`**. By adding the `abpCapsLock` event to an element, you can track the status of Caps Lock. You can use this to warn user.
See an example usage:
**NgModule Component usage**
```ts
@Component({
selector: 'test-component',
template: `
<div class="d-flex flex-column">
<label>Password</label>
<input (abpCapsLock)="capsLock = $event"/>
<i *ngIf="capsLock">icon</i>
</div>
`
})
export class TestComponent{
capsLock = false;
}
```
**Standalone Component usage**
```ts
import { TrackCapsLockDirective } from '@abp/ng.core'
@Component({
selector: 'standalone-component',
standalone: true,
template: `
<div class="d-flex flex-column">
<label>Password</label>
<input (abpCapsLock)="capsLock = $event"/>
<i *ngIf="capsLock">icon</i>
</div>
`,
imports: [TrackCapsLockDirective]
})
export class StandaloneComponent{
capsLock = false;
}
```
The `abpCapsLock` event has been added to the `<input>` element. Press Caps Lock to activate the `TrackCapsLockDirective`.
See the result:
![Show Password directive](./images/CapsLockDirective1.png)
To see Caps Lock icon press Caps Lock.
![Show Password directive](./images/CapsLockDirective2.png)

@ -0,0 +1,77 @@
# Show Password Directive
In password input, text can be shown easily via changing input type attribute to `text`. To make this even easier, you can use the `ShowPasswordDirective` which has been exposed by the `@abp/ng.core` package.
## Getting Started
`ShowPasswordDirective` is standalone. In order to use the `ShowPasswordDirective` in an HTML template, import it to related module or your standalone component:
**Importing to NgModule**
```ts
import { ShowPasswordDirective } from '@abp/ng.core';
@NgModule({
//...
declarations: [
...,
TestComponent
],
imports: [
...,
ShowPasswordDirective
],
})
export class MyFeatureModule {}
```
## Usage
The `ShowPasswordDirective` is very easy to use. The directive's selector is **`abpShowPassword`**. By adding the `abpShowPassword` attribute to an input element, you can activate the `ShowPasswordDirective` for the input element.
See an example usage:
**NgModule Component usage**
```ts
@Component({
selector: 'test-component',
template: `
<div class="d-flex flex-column">
<label>Password</label>
<input [abpShowPassword]="showPassword"/>
<i (click)="showPassword = !showPassword">icon</i>
</div>
`
})
export class TestComponent{
showPassword = false;
}
```
**Standalone Component usage**
```ts
import { ShowPasswordDirective } from '@abp/ng.core';
@Component({
selector: 'standalone-component',
standalone: true,
template: `
<div class="d-flex flex-column">
<label>Password</label>
<input [abpShowPassword]="showPassword"/>
<i (click)="showPassword = !showPassword">icon</i>
</div>
`,
imports: [ShowPasswordDirective]
})
export class StandaloneComponent{
showPassword = false;
}
```
The `abpShowPassword` attribute has been added to the `<input>` element. Click icon to activate the `ShowPasswordDirective`.
See the result:
![Show Password directive](./images/showPasswordDirective1.png)
To see password input click icon.
![Show Password directive](./images/showPasswordDirective2.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -1522,6 +1522,10 @@
}
]
},
{
"text": "API Documentation",
"path": "{ApiDocumentationUrl}"
},
{
"text": "Contribution Guide",
"path": "Contribution/Index.md"

@ -146,7 +146,8 @@ public class BookStoreSettingPageContributor : ISettingPageContributor
new SettingPageGroup(
"Volo.Abp.MySettingGroup",
"MySettingGroup",
typeof(MySettingGroupViewComponent)
typeof(MySettingGroupViewComponent),
order : 1
)
);
@ -209,7 +210,8 @@ public class BookStoreSettingComponentContributor : ISettingComponentContributor
new SettingComponentGroup(
"Volo.Abp.MySettingGroup",
"MySettingGroup",
typeof(MySettingGroupComponent)
typeof(MySettingGroupComponent),
order : 1
)
);

@ -15,7 +15,7 @@ public static class AbpHostingHostBuilderExtensions
return hostBuilder.ConfigureAppConfiguration((_, builder) =>
{
builder.AddJsonFile(
path: AppSettingsSecretJsonPath,
path: path,
optional: optional,
reloadOnChange: reloadOnChange
);

@ -1,8 +0,0 @@
using System.Threading.Tasks;
namespace Volo.Abp.Http.Client.IdentityModel.MauiBlazor;
public interface IAbpMauiAccessTokenProvider
{
Task<string> GetAccessTokenAsync();
}

@ -0,0 +1,14 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Authentication;
namespace Volo.Abp.Http.Client.IdentityModel.MauiBlazor;
[Dependency(ReplaceServices = true)]
public class MauiBlazorAbpAccessTokenProvider : IAbpAccessTokenProvider, ITransientDependency
{
public virtual Task<string> GetTokenAsync()
{
return Task.FromResult(null as string);
}
}

@ -1,8 +1,5 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
using IdentityModel.Client;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Authentication;
using Volo.Abp.IdentityModel;
@ -12,22 +9,21 @@ namespace Volo.Abp.Http.Client.IdentityModel.MauiBlazor;
[Dependency(ReplaceServices = true)]
public class MauiBlazorIdentityModelRemoteServiceHttpClientAuthenticator : IdentityModelRemoteServiceHttpClientAuthenticator
{
[CanBeNull]
protected IAbpMauiAccessTokenProvider AbpMauiAccessTokenProvider { get; }
protected IAbpAccessTokenProvider AccessTokenProvider { get; }
public MauiBlazorIdentityModelRemoteServiceHttpClientAuthenticator(
IIdentityModelAuthenticationService identityModelAuthenticationService,
IServiceProvider serviceProvider)
IAbpAccessTokenProvider abpAccessTokenProvider)
: base(identityModelAuthenticationService)
{
AbpMauiAccessTokenProvider = serviceProvider.GetService<IAbpMauiAccessTokenProvider>();
AccessTokenProvider = abpAccessTokenProvider;
}
public async override Task Authenticate(RemoteServiceHttpClientAuthenticateContext context)
{
if (context.RemoteService.GetUseCurrentAccessToken() != false)
{
var accessToken = await GetAccessTokenFromAccessTokenProviderOrNullAsync();
var accessToken = await AccessTokenProvider.GetTokenAsync();
if (accessToken != null)
{
context.Request.SetBearerToken(accessToken);
@ -37,14 +33,4 @@ public class MauiBlazorIdentityModelRemoteServiceHttpClientAuthenticator : Ident
await base.Authenticate(context);
}
protected virtual async Task<string> GetAccessTokenFromAccessTokenProviderOrNullAsync()
{
if (AbpMauiAccessTokenProvider == null)
{
return null;
}
return await AbpMauiAccessTokenProvider.GetAccessTokenAsync();
}
}

@ -1,13 +0,0 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Http.Client.IdentityModel.MauiBlazor;
[Dependency(TryRegister = true)]
public class NullAbpMauiAccessTokenProvider : IAbpMauiAccessTokenProvider
{
public Task<string> GetAccessTokenAsync()
{
return Task.FromResult(null as string);
}
}

@ -1,4 +1,5 @@
using Volo.Abp.Modularity;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
namespace Volo.Abp.Http.Client.IdentityModel.Web;
@ -7,5 +8,8 @@ namespace Volo.Abp.Http.Client.IdentityModel.Web;
)]
public class AbpHttpClientIdentityModelWebModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddHttpContextAccessor();
}
}

@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Authentication;
namespace Volo.Abp.Http.Client.IdentityModel.Web;
[Dependency(ReplaceServices = true)]
public class HttpContextAbpAccessTokenProvider : IAbpAccessTokenProvider, ITransientDependency
{
protected IHttpContextAccessor HttpContextAccessor { get; }
public HttpContextAbpAccessTokenProvider(IHttpContextAccessor httpContextAccessor)
{
HttpContextAccessor = httpContextAccessor;
}
public virtual async Task<string> GetTokenAsync()
{
var httpContext = HttpContextAccessor?.HttpContext;
if (httpContext == null)
{
return null;
}
return await httpContext.GetTokenAsync("access_token");
}
}

@ -1,7 +1,5 @@
using System.Threading.Tasks;
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Authentication;
using Volo.Abp.IdentityModel;
@ -11,19 +9,21 @@ namespace Volo.Abp.Http.Client.IdentityModel.Web;
[Dependency(ReplaceServices = true)]
public class HttpContextIdentityModelRemoteServiceHttpClientAuthenticator : IdentityModelRemoteServiceHttpClientAuthenticator
{
public IHttpContextAccessor HttpContextAccessor { get; set; }
protected IAbpAccessTokenProvider AccessTokenProvider { get; }
public HttpContextIdentityModelRemoteServiceHttpClientAuthenticator(
IIdentityModelAuthenticationService identityModelAuthenticationService)
IIdentityModelAuthenticationService identityModelAuthenticationService,
IAbpAccessTokenProvider accessTokenProvider)
: base(identityModelAuthenticationService)
{
AccessTokenProvider = accessTokenProvider;
}
public override async Task Authenticate(RemoteServiceHttpClientAuthenticateContext context)
public async override Task Authenticate(RemoteServiceHttpClientAuthenticateContext context)
{
if (context.RemoteService.GetUseCurrentAccessToken() != false)
{
var accessToken = await GetAccessTokenFromHttpContextOrNullAsync();
var accessToken = await AccessTokenProvider.GetTokenAsync();
if (accessToken != null)
{
context.Request.SetBearerToken(accessToken);
@ -33,15 +33,4 @@ public class HttpContextIdentityModelRemoteServiceHttpClientAuthenticator : Iden
await base.Authenticate(context);
}
protected virtual async Task<string> GetAccessTokenFromHttpContextOrNullAsync()
{
var httpContext = HttpContextAccessor?.HttpContext;
if (httpContext == null)
{
return null;
}
return await httpContext.GetTokenAsync("access_token");
}
}

@ -1,9 +1,5 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
using IdentityModel.Client;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Authentication;
using Volo.Abp.IdentityModel;
@ -14,22 +10,21 @@ namespace Volo.Abp.Http.Client.IdentityModel.WebAssembly;
public class AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticator
: IdentityModelRemoteServiceHttpClientAuthenticator
{
[CanBeNull]
protected IAccessTokenProvider AccessTokenProvider { get; }
protected IAbpAccessTokenProvider AccessTokenProvider { get; }
public AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticator(
IIdentityModelAuthenticationService identityModelAuthenticationService,
IServiceProvider serviceProvider)
IAbpAccessTokenProvider accessTokenProvider)
: base(identityModelAuthenticationService)
{
AccessTokenProvider = serviceProvider.GetService<IAccessTokenProvider>();
AccessTokenProvider = accessTokenProvider;
}
public override async Task Authenticate(RemoteServiceHttpClientAuthenticateContext context)
public async override Task Authenticate(RemoteServiceHttpClientAuthenticateContext context)
{
if (context.RemoteService.GetUseCurrentAccessToken() != false)
{
var accessToken = await GetAccessTokenFromAccessTokenProviderOrNullAsync();
var accessToken = await AccessTokenProvider.GetTokenAsync();
if (accessToken != null)
{
context.Request.SetBearerToken(accessToken);
@ -39,21 +34,4 @@ public class AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticato
await base.Authenticate(context);
}
protected virtual async Task<string> GetAccessTokenFromAccessTokenProviderOrNullAsync()
{
if (AccessTokenProvider == null)
{
return null;
}
var result = await AccessTokenProvider.RequestAccessToken();
if (result.Status != AccessTokenResultStatus.Success)
{
return null;
}
result.TryGetToken(out var token);
return token.Value;
}
}

@ -0,0 +1,34 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Authentication;
namespace Volo.Abp.Http.Client.IdentityModel.WebAssembly;
[Dependency(ReplaceServices = true)]
public class WebAssemblyAbpAccessTokenProvider : IAbpAccessTokenProvider, ITransientDependency
{
protected IAccessTokenProvider AccessTokenProvider { get; }
public WebAssemblyAbpAccessTokenProvider(IAccessTokenProvider accessTokenProvider)
{
AccessTokenProvider = accessTokenProvider;
}
public virtual async Task<string> GetTokenAsync()
{
if (AccessTokenProvider == null)
{
return null;
}
var result = await AccessTokenProvider.RequestAccessToken();
if (result.Status != AccessTokenResultStatus.Success)
{
return null;
}
result.TryGetToken(out var token);
return token.Value;
}
}

@ -0,0 +1,8 @@
using System.Threading.Tasks;
namespace Volo.Abp.Http.Client.Authentication;
public interface IAbpAccessTokenProvider
{
Task<string> GetTokenAsync();
}

@ -0,0 +1,12 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Http.Client.Authentication;
public class NullAbpAccessTokenProvider : IAbpAccessTokenProvider, ITransientDependency
{
public Task<string> GetTokenAsync()
{
return Task.FromResult(null as string);
}
}

@ -5,7 +5,7 @@ using Volo.Abp.Modularity;
namespace Volo.Abp.BackgroundJobs.DemoApp.Shared
{
[DependsOn(
typeof(AbpBackgroundJobsAbstractionsModule)
typeof(AbpBackgroundJobsModule)
)]
public class DemoAppSharedModule : AbpModule
{

@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftPackageVersion)" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.BackgroundJobs.Abstractions\Volo.Abp.BackgroundJobs.Abstractions.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.BackgroundJobs\Volo.Abp.BackgroundJobs.csproj" />
</ItemGroup>
</Project>

@ -16,7 +16,8 @@ public class AbpIdentityAspNetCoreModule : AbpModule
builder
.AddDefaultTokenProviders()
.AddTokenProvider<LinkUserTokenProvider>(LinkUserTokenProviderConsts.LinkUserTokenProviderName)
.AddSignInManager<AbpSignInManager>();
.AddSignInManager<AbpSignInManager>()
.AddUserValidator<AbpIdentityUserValidator>();
});
}

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "اسم الدور '{0}' غير صالح.",
"Volo.Abp.Identity:InvalidToken": "غير صالح token.",
"Volo.Abp.Identity:InvalidUserName": "اسم المستخدم '{0}' غير صالح ، يمكن أن يحتوي على أحرف أو أرقام فقط.",
"InvalidUserName": "لااسم المستخدم '{0}' غير صالح .",
"Volo.Abp.Identity:LoginAlreadyAssociated": "يوجد مستخدم لديه معلومات تسجيل الدخول هذه بالفعل.",
"Volo.Abp.Identity:PasswordMismatch": "كلمة مرور غير صحيحة.",
"Volo.Abp.Identity:PasswordRequiresDigit": "يجب أن تحتوي كلمات المرور على رقم واحد على الأقل ('0' - '9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Role '{0}' je neplatná.",
"Volo.Abp.Identity:InvalidToken": "Neplatný token.",
"Volo.Abp.Identity:InvalidUserName": "Uživatelské jméno '{0}' je neplatné, může obsahovat pouze písmena a číslice.",
"InvalidUserName": "Uživatelské jméno '{0}' je neplatné.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Uživatel s tímto přihlášením již existuje.",
"Volo.Abp.Identity:PasswordMismatch": "Chybné heslo.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Hesla musí obsahovat alespoň jednu číslici ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Der Rollenname '{0}' ist ungültig.",
"Volo.Abp.Identity:InvalidToken": "Ungültiger Token.",
"Volo.Abp.Identity:InvalidUserName": "Der Benutzername '{0}' ist ungültig und darf nur Buchstaben oder Ziffern enthalten.",
"InvalidUserName": "Der Benutzername '{0}' ist ungültig.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Ein Benutzer mit diesem Login existiert bereits.",
"Volo.Abp.Identity:PasswordMismatch": "Falsches Passwort.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Passwörter müssen mindestens eine Ziffer haben ('0' - '9').",

@ -47,6 +47,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Το όνομα ρόλου '{0}' δεν είναι έγκυρο.",
"Volo.Abp.Identity:InvalidToken": "Μη έγκυρο διακριτικό.",
"Volo.Abp.Identity:InvalidUserName": "Το όνομα χρήστη '{0}' δεν είναι έγκυρο, μπορεί να περιέχει μόνο γράμματα ή ψηφία.",
"InvalidUserName": "Το όνομα χρήστη '{0}' δεν είναι έγκυρο.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Ένας χρήστης με αυτήν τη σύνδεση υπάρχει ήδη.",
"Volo.Abp.Identity:PasswordMismatch": "Λάθος κωδικός.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Οι κωδικοί πρόσβασης πρέπει να έχουν τουλάχιστον ένα ψηφίο ('0'-'9').",

@ -47,6 +47,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Role name '{0}' is invalid.",
"Volo.Abp.Identity:InvalidToken": "Invalid token.",
"Volo.Abp.Identity:InvalidUserName": "Username '{0}' is invalid, it should only contain letters or digits.",
"InvalidUserName": "Username '{0}' is invalid.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "A user with this login already exists.",
"Volo.Abp.Identity:PasswordMismatch": "Incorrect password.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Passwords must have at least one digit ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Role name '{0}' is invalid.",
"Volo.Abp.Identity:InvalidToken": "Invalid token.",
"Volo.Abp.Identity:InvalidUserName": "Username '{0}' is invalid, can only contain letters or digits.",
"InvalidUserName": "Username '{0}' is invalid.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "A user with this login already exists.",
"Volo.Abp.Identity:PasswordMismatch": "Incorrect password.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Passwords must have at least one digit ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "El nombre de rol '{0}' no es válido.",
"Volo.Abp.Identity:InvalidToken": "token no válido",
"Volo.Abp.Identity:InvalidUserName": "Nombre de usuario '{0}' no es válido. Sólo puede contener letras o dígitos",
"InvalidUserName": "Nombre de usuario '{0}' no es válido.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Usuario con este inicio de sesión en uso.",
"Volo.Abp.Identity:PasswordMismatch": "Contraseña incorrecta.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Contraseñas deben tener al menos un dígito ('0'-'9').",

@ -47,6 +47,7 @@
"Volo.Abp.Identity:InvalidRoleName": "عنوان نقش/وظیفه {0} نامعتبر است.",
"Volo.Abp.Identity:InvalidToken": "گذرواژه وارد شده معتبر نیست.",
"Volo.Abp.Identity:InvalidUserName": "نام کاربری '{0}' نامعتبر است، نام کاربری فقط می تواند شامل حروف یا ارقام باشد.",
"InvalidUserName": "ام کاربری '{0}' نامعتبر است",
"Volo.Abp.Identity:LoginAlreadyAssociated": "یک کاربر با این نام کاربری از قبل موجود می باشد.",
"Volo.Abp.Identity:PasswordMismatch": "گذرواژه اشتباه است.",
"Volo.Abp.Identity:PasswordRequiresDigit": "گذرواژه ها باید حداقل یک رقم داشته باشند ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Roolin nimi {0} on virheellinen.",
"Volo.Abp.Identity:InvalidToken": "Virheellinen tunnus.",
"Volo.Abp.Identity:InvalidUserName": "Käyttäjätunnus {0} on virheellinen, voi sisältää vain kirjaimia tai numeroita.",
"InvalidUserName": "Käyttäjätunnus {0} on virheellinen.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Käyttäjä, jolla on tämä sisäänkirjautuminen, on jo olemassa.",
"Volo.Abp.Identity:PasswordMismatch": "Väärä salasana.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Salasanoissa on oltava vähintään yksi numero ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Le nom de rôle '{0}' nest pas valide.",
"Volo.Abp.Identity:InvalidToken": "Jeton non valide.",
"Volo.Abp.Identity:InvalidUserName": "Le nom dutilisateur '{0}' nest pas valide, ne peut contenir que des lettres ou des chiffres.",
"InvalidUserName": "Le nom dutilisateur '{0}' nest pas valide.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Un utilisateur avec cette connexion existe déjà.",
"Volo.Abp.Identity:PasswordMismatch": "Mot de passe incorrect.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Les mots de passe doivent avoir au moins un chiffre ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "भूमिका नाम '{0}' अमान्य है।",
"Volo.Abp.Identity:InvalidToken": "अमान्य टोकन।",
"Volo.Abp.Identity:InvalidUserName": "उपयोगकर्ता नाम '{0}' अमान्य है, इसमें केवल अक्षर या अंक हो सकते हैं।",
"InvalidUserName": "उपयोगकर्ता नाम '{0}' अमान्य है",
"Volo.Abp.Identity:LoginAlreadyAssociated": "इस लॉगिन वाला उपयोगकर्ता पहले से मौजूद है।",
"Volo.Abp.Identity:PasswordMismatch": "गलत पासवर्ड।",
"Volo.Abp.Identity:PasswordRequiresDigit": "पासवर्ड में कम से कम एक अंक ('0'-'9') होना चाहिए।",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Naziv uloge '{0}' nije valjan.",
"Volo.Abp.Identity:InvalidToken": "Pogrešan token.",
"Volo.Abp.Identity:InvalidUserName": "Korisničko ime '{0}' nije važeće, može sadržavati samo slova ili znamenke.",
"InvalidUserName": "Korisničko ime '{0}' nije važeće.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Korisnik s ovom prijavom već postoji.",
"Volo.Abp.Identity:PasswordMismatch": "Netočna lozinka.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Lozinke moraju imati najmanje jednu znamenku ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "'{0}' szerepkör név érvénytelen.",
"Volo.Abp.Identity:InvalidToken": "Érvénytelen token.",
"Volo.Abp.Identity:InvalidUserName": "'{0}' felhasználónév érvénytelen, csak betűket és számokat tartalmazhat.",
"InvalidUserName": "'{0}' felhasználónév érvénytelen.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Az ilyen bejelentkezéssel rendelkező felhasználó már létezik.",
"Volo.Abp.Identity:PasswordMismatch": "Érvénytelen jelszó.",
"Volo.Abp.Identity:PasswordRequiresDigit": "A jelszavaknak legalább egy számjegyet kell rendelkezniük ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Hlutverkanafnið '{0}' er ógilt.",
"Volo.Abp.Identity:InvalidToken": "Ógilt token.",
"Volo.Abp.Identity:InvalidUserName": "Notandanafnið '{0}' er ógilt, getur aðeins innihaldið bókstafi eða tölustafi.",
"InvalidUserName": "Notandanafnið '{0}' er ógilt.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Notandi með þessa innskráningu er þegar til.",
"Volo.Abp.Identity:PasswordMismatch": "Rangt lykilorð.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Lykilorð verða að hafa að minnsta kosti einn staf ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Il nome del ruolo '{0}' non è valido.",
"Volo.Abp.Identity:InvalidToken": "Token non valido.",
"Volo.Abp.Identity:InvalidUserName": "Il nome utente '{0}' non è valido, può contenere solo lettere o cifre.",
"InvalidUserName": "Il nome utente '{0}' non è valido.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Esiste già un utente con questo account.",
"Volo.Abp.Identity:PasswordMismatch": "Password errata.",
"Volo.Abp.Identity:PasswordRequiresDigit": "La password deve contenere almeno una cifra ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Rol naam '{0}' is ongeldig.",
"Volo.Abp.Identity:InvalidToken": "Ongeldig token.",
"Volo.Abp.Identity:InvalidUserName": "Gebruikersnaam '{0}' is ongeldig, mag alleen letters of cijfers bevatten.",
"InvalidUserName": "Gebruikersnaam '{0}' is ongeldig.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Er bestaat al een gebruiker met deze login.",
"Volo.Abp.Identity:PasswordMismatch": "Incorrect wachtwoord.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Wachtwoorden moeten minimaal één cijfer bevatten ('0' - '9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Rola '{0}' jest niepoprawna.",
"Volo.Abp.Identity:InvalidToken": "Niepoprawny token.",
"Volo.Abp.Identity:InvalidUserName": "Nazwa użytkownika '{0}' jest niepoprawna, może zawierać tylko litery i cyfry.",
"InvalidUserName": "Nazwa użytkownika '{0}' jest niepoprawna.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Użytkownik o tym loginie już istnieje.",
"Volo.Abp.Identity:PasswordMismatch": "Niepoprawne hasło.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Hasło musi zawierać przynajmniej jedną cyfrę ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Perfil '{0}' inválido.",
"Volo.Abp.Identity:InvalidToken": "Token inválido.",
"Volo.Abp.Identity:InvalidUserName": "Usuário '{0}' é inválido, somente pode conter letras ou números.",
"InvalidUserName": "Usuário '{0}' é inválido.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Um usuário com este login já existe.",
"Volo.Abp.Identity:PasswordMismatch": "Senha incorreta.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Senhas devem possuir pelo menos um dígito ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Numele de rol '{0}' este invalid.",
"Volo.Abp.Identity:InvalidToken": "Invalid token.",
"Volo.Abp.Identity:InvalidUserName": "Numele de utilizator '{0}' este invalid, poate conţine doar litere şi cifre.",
"InvalidUserName": "Numele de utilizator '{0}' este invalid.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Un utilizator cu această autentificare există deja.",
"Volo.Abp.Identity:PasswordMismatch": "Parolă incorectă.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Parolele trebuie să conţină cel puţin o cifră ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Имя роли '{0}' недопустимо.",
"Volo.Abp.Identity:InvalidToken": "Недопустимый маркер.",
"Volo.Abp.Identity:InvalidUserName": "Имя пользователя '{0}' недопустимо и может содержать только буквы или цифры.",
"InvalidUserName": "Имя пользователя '{0}' недопустимо.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Пользователь с таким логином уже существует.",
"Volo.Abp.Identity:PasswordMismatch": "Неверный пароль.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Пароль должен содержать по крайней мере одну цифру.",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Názov roly '{0}' je neplatný.",
"Volo.Abp.Identity:InvalidToken": "Neplatný token.",
"Volo.Abp.Identity:InvalidUserName": "Používateľské meno '{0}' je neplatné, môže obsahovať len písmená alebo číslice.",
"InvalidUserName": "Používateľské meno '{0}' je neplatné.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Používateľ s týmto prihlasovacím menom už existuje.",
"Volo.Abp.Identity:PasswordMismatch": "Nesprávne heslo.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Heslá musia obsahovať aspoň jednu číslicu ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Naziv vloge '{0}' ni veljaven.",
"Volo.Abp.Identity:InvalidToken": "Neveljaven žeton.",
"Volo.Abp.Identity:InvalidUserName": "Uporabniško ime '{0}' ni veljavno, vsebuje lahko le črke in številke.",
"InvalidUserName": "Uporabniško ime '{0}' ni veljavno.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Uporabnik s to prijavo že obstaja.",
"Volo.Abp.Identity:PasswordMismatch": "Nepravilno geslo.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Gesla morajo imeti vsaj eno številko ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "'{0}' rol ismi geçersizdir.",
"Volo.Abp.Identity:InvalidToken": "Geçersiz token.",
"Volo.Abp.Identity:InvalidUserName": "'{0}' kullanıcı adı geçersiz, sadece harf ve rakamlardan oluşmalı.",
"InvalidUserName": "'{0}' kullanıcı adı geçersiz.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Bu giriş bilgilerine sahip bir kullanıcı zaten var.",
"Volo.Abp.Identity:PasswordMismatch": "Hatalı şifre.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Şifre en az bir sayı içermeli ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "Tên người dùng '{0}' Không hợp lệ.",
"Volo.Abp.Identity:InvalidToken": "Token không hợp lệ.",
"Volo.Abp.Identity:InvalidUserName": "Tên người dùng '{0}' không hợp lệ, chỉ có thể chứa chữ cái hoặc chữ số.",
"InvalidUserName": "Tên người dùng '{0}' không hợp lệ.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "Một người dùng có thông tin đăng nhập này đã tồn tại.",
"Volo.Abp.Identity:PasswordMismatch": "Mật khẩu không đúng.",
"Volo.Abp.Identity:PasswordRequiresDigit": "Mật khẩu phải có ít nhất một chữ số ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "角色名 '{0}' 无效.",
"Volo.Abp.Identity:InvalidToken": "token无效.",
"Volo.Abp.Identity:InvalidUserName": "用户名 '{0}' 无效, 只能包含字母或数字.",
"InvalidUserName": "用户名 '{0}' 无效.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "此登录名的用户已存在.",
"Volo.Abp.Identity:PasswordMismatch": "密码错误.",
"Volo.Abp.Identity:PasswordRequiresDigit": "密码至少包含一位数字 ('0'-'9').",

@ -48,6 +48,7 @@
"Volo.Abp.Identity:InvalidRoleName": "角色名 '{0}' 無效.",
"Volo.Abp.Identity:InvalidToken": "token無效.",
"Volo.Abp.Identity:InvalidUserName": "使用者名稱 '{0}' 無效, 只能包含字母或數字.",
"InvalidUserName": "使用者名稱 '{0}' 無效.",
"Volo.Abp.Identity:LoginAlreadyAssociated": "此登入名的使用者已存在.",
"Volo.Abp.Identity:PasswordMismatch": "密碼錯誤.",
"Volo.Abp.Identity:PasswordRequiresDigit": "密碼至少包含一位數字 ('0'-'9').",

@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Localization;
using Volo.Abp.Identity.Localization;
namespace Volo.Abp.Identity
{
public class AbpIdentityUserValidator : IUserValidator<IdentityUser>
{
protected IStringLocalizer<IdentityResource> Localizer { get; }
public AbpIdentityUserValidator(IStringLocalizer<IdentityResource> localizer)
{
Localizer = localizer;
}
public virtual async Task<IdentityResult> ValidateAsync(UserManager<IdentityUser> manager, IdentityUser user)
{
var describer = new IdentityErrorDescriber();
Check.NotNull(manager, nameof(manager));
Check.NotNull(user, nameof(user));
var errors = new List<IdentityError>();
var userName = await manager.GetUserNameAsync(user);
if (userName == null)
{
errors.Add(describer.InvalidUserName(null));
}
else
{
var owner = await manager.FindByEmailAsync(userName);
if (owner != null && !string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user)))
{
errors.Add(new IdentityError
{
Code = "InvalidUserName",
Description = Localizer["InvalidUserName", userName]
});
}
}
return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success;
}
}
}

@ -0,0 +1,36 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using Shouldly;
using Volo.Abp.Identity.Localization;
using Xunit;
namespace Volo.Abp.Identity.AspNetCore;
public class AbpIdentityUserValidator_Tests : AbpIdentityAspNetCoreTestBase
{
private readonly IdentityUserManager _identityUserManager;
private readonly IStringLocalizer<IdentityResource> Localizer;
public AbpIdentityUserValidator_Tests()
{
_identityUserManager = GetRequiredService<IdentityUserManager>();
Localizer = GetRequiredService<IStringLocalizer<IdentityResource>>();
}
[Fact]
public async Task Can_Not_Use_Another_Users_Email_As_Your_Username_Test()
{
var user1 = new IdentityUser(Guid.NewGuid(), "user1", "user1@volosoft.com");
var identityResult = await _identityUserManager.CreateAsync(user1);
identityResult.Succeeded.ShouldBeTrue();
var user2 = new IdentityUser(Guid.NewGuid(), "user1@volosoft.com", "user2@volosoft.com");
identityResult = await _identityUserManager.CreateAsync(user2);
identityResult.Succeeded.ShouldBeFalse();
identityResult.Errors.Count().ShouldBe(1);
identityResult.Errors.First().Code.ShouldBe("InvalidUserName");
identityResult.Errors.First().Description.ShouldBe(Localizer["InvalidUserName", "user1@volosoft.com"]);
}
}

@ -39,7 +39,7 @@ public partial class SettingManagement
{
await contributor.ConfigureAsync(SettingComponentCreationContext);
}
SettingComponentCreationContext.Normalize();
SettingItemRenders.Clear();
SelectedGroup = GetNormalizedString(SettingComponentCreationContext.Groups.First().Id);

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.SettingManagement.Blazor;
@ -8,7 +9,7 @@ public class SettingComponentCreationContext : IServiceProviderAccessor
{
public IServiceProvider ServiceProvider { get; }
public List<SettingComponentGroup> Groups { get; }
public List<SettingComponentGroup> Groups { get; private set; }
public SettingComponentCreationContext(IServiceProvider serviceProvider)
{
@ -16,4 +17,14 @@ public class SettingComponentCreationContext : IServiceProviderAccessor
Groups = new List<SettingComponentGroup>();
}
public void Normalize()
{
Order();
}
private void Order()
{
Groups = Groups.OrderBy(item => item.Order).ThenBy(item => item.DisplayName).ToList();
}
}

@ -5,6 +5,8 @@ namespace Volo.Abp.SettingManagement.Blazor;
public class SettingComponentGroup
{
public const int DefaultOrder = 1000;
public string Id {
get => _id;
set => _id = Check.NotNullOrWhiteSpace(value, nameof(Id));
@ -25,11 +27,14 @@ public class SettingComponentGroup
public object Parameter { get; set; }
public SettingComponentGroup([NotNull] string id, [NotNull] string displayName, [NotNull] Type componentType, object parameter = null)
public int Order { get; set; }
public SettingComponentGroup([NotNull] string id, [NotNull] string displayName, [NotNull] Type componentType, object parameter = null, int order = DefaultOrder)
{
Id = id;
DisplayName = displayName;
ComponentType = componentType;
Parameter = parameter;
Order = order;
}
}

@ -37,6 +37,7 @@ public class SettingPageContributorManager : IScopedDependency
{
await contributor.ConfigureAsync(context);
}
context.Normalize();
return context;
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.SettingManagement.Web.Pages.SettingManagement;
@ -8,7 +9,7 @@ public class SettingPageCreationContext : IServiceProviderAccessor
{
public IServiceProvider ServiceProvider { get; }
public List<SettingPageGroup> Groups { get; }
public List<SettingPageGroup> Groups { get; private set; }
public SettingPageCreationContext(IServiceProvider serviceProvider)
{
@ -16,4 +17,14 @@ public class SettingPageCreationContext : IServiceProviderAccessor
Groups = new List<SettingPageGroup>();
}
public void Normalize()
{
Order();
}
private void Order()
{
Groups = Groups.OrderBy(item => item.Order).ThenBy(item => item.DisplayName).ToList();
}
}

@ -5,6 +5,8 @@ namespace Volo.Abp.SettingManagement.Web.Pages.SettingManagement;
public class SettingPageGroup
{
public const int DefaultOrder = 1000;
public string Id {
get => _id;
set => _id = Check.NotNullOrWhiteSpace(value, nameof(Id));
@ -25,11 +27,14 @@ public class SettingPageGroup
public object Parameter { get; set; }
public SettingPageGroup([NotNull] string id, [NotNull] string displayName, [NotNull] Type componentType, object parameter = null)
public int Order { get; set; }
public SettingPageGroup([NotNull] string id, [NotNull] string displayName, [NotNull] Type componentType, object parameter = null, int order = DefaultOrder)
{
Id = id;
DisplayName = displayName;
ComponentType = componentType;
Parameter = parameter;
Order = order;
}
}

@ -38,6 +38,7 @@ import { SafeHtmlPipe } from './pipes/safe-html.pipe';
import { QUEUE_MANAGER } from './tokens/queue.token';
import { DefaultQueueManager } from './utils/queue';
import { IncludeLocalizationResourcesProvider } from './providers/include-localization-resources.provider';
import { SORT_COMPARE_FUNC, compareFuncFactory } from './tokens/compare-func.token';
/**
* BaseCoreModule is the module that holds
@ -176,6 +177,10 @@ export class CoreModule {
useValue: localizationContributor(options.localizations),
deps: [LocalizationService],
},
{
provide: SORT_COMPARE_FUNC,
useFactory: compareFuncFactory
},
{
provide: QUEUE_MANAGER,
useClass: DefaultQueueManager,

@ -12,6 +12,7 @@ import {
} from '../utils/tree-utils';
import { ConfigStateService } from './config-state.service';
import { PermissionService } from './permission.service';
import { SORT_COMPARE_FUNC } from '../tokens/compare-func.token';
// eslint-disable-next-line @typescript-eslint/ban-types
export abstract class AbstractTreeService<T extends { [key: string | number | symbol]: any }> {
@ -158,14 +159,12 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
{
private subscription: Subscription;
private permissionService: PermissionService;
private compareFunc;
readonly id = 'name';
readonly parentId = 'parentName';
readonly hide = (item: T) => item.invisible || !this.isGranted(item);
readonly sort = (a: T, b: T) => {
if (!Number.isInteger(a.order)) return 1;
if (!Number.isInteger(b.order)) return -1;
return (a.order as number) - (b.order as number);
return this.compareFunc(a,b)
};
constructor(protected injector: Injector) {
@ -176,6 +175,7 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
.subscribe(() => this.refresh());
this.permissionService = injector.get(PermissionService);
this.othersGroup = injector.get(OTHERS_GROUP);
this.compareFunc = injector.get(SORT_COMPARE_FUNC);
}
protected isGranted({ requiredPolicy }: T): boolean {

@ -0,0 +1,57 @@
import { Component, DebugElement } from '@angular/core'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { TrackCapsLockDirective } from '../directives';
import { By } from '@angular/platform-browser';
@Component({
standalone:true,
template: `
<input (abpCapsLock)="capsLock = $event" />
`,
imports:[TrackCapsLockDirective]
})
class TestComponent {
capsLock:boolean = false
}
describe('TrackCapsLockDirective',()=>{
let fixture: ComponentFixture<TestComponent>;;
let des : DebugElement[];
beforeEach(()=>{
fixture = TestBed.configureTestingModule({
imports: [ TestComponent ]
}).createComponent(TestComponent);
fixture.detectChanges();
des = fixture.debugElement.queryAll(By.directive(TrackCapsLockDirective));
});
test.each(['keydown','keyup'])('is %p works when press capslock and is emit status', (eventName) => {
const event = new KeyboardEvent(eventName, {
key: 'CapsLock',
modifierCapsLock: true
});
window.dispatchEvent(event);
fixture.detectChanges();
expect(fixture.componentInstance.capsLock).toBe(true)
});
test.each(['keydown','keyup'])('is %p detect the change capslock is emit status', (eventName) => {
const trueEvent = new KeyboardEvent(eventName, {
key: 'CapsLock',
modifierCapsLock: true
});
window.dispatchEvent(trueEvent);
fixture.detectChanges();
expect(fixture.componentInstance.capsLock).toBe(true)
const falseEvent = new KeyboardEvent(eventName, {
key: 'CapsLock',
modifierCapsLock: false
});
window.dispatchEvent(falseEvent);
fixture.detectChanges();
expect(fixture.componentInstance.capsLock).toBe(false)
});
});

@ -0,0 +1,55 @@
import { Component, DebugElement } from '@angular/core'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ShowPasswordDirective } from '../directives';
import { By } from '@angular/platform-browser';
@Component({
standalone:true,
template: `
<input [abpShowPassword]="true">
<input [abpShowPassword]="false">
<input />
<input [abpShowPassword]="showPassword" />`,
imports:[ShowPasswordDirective]
})
class TestComponent {
showPassword:boolean = false
}
describe('ShowPasswordDirective',()=>{
let fixture: ComponentFixture<TestComponent>;;
let des : DebugElement[];
let desAll : DebugElement[];
let bareInput;
beforeEach(()=>{
fixture = TestBed.configureTestingModule({
imports: [ TestComponent ]
}).createComponent(TestComponent)
fixture.detectChanges();
des = fixture.debugElement.queryAll(By.directive(ShowPasswordDirective));
desAll = fixture.debugElement.queryAll(By.all());
bareInput = fixture.debugElement.query(By.css('input:not([abpShowPassword])'));
})
it('should have three input has ShowPasswordDirective elements', () => {
expect(des.length).toBe(3);
});
test.each([[0,'text'],[1,'password'],[2,'text'],[3,'password']])('%p. input type must be %p)', (index,inpType) => {
const inputType = desAll[index].nativeElement.type;
expect(inputType).toBe(inpType);
});
it('should have three input has ShowPasswordDirective elements', () => {
let input = des[2].nativeElement
expect(input.type).toBe('password')
fixture.componentInstance.showPassword = true
fixture.detectChanges()
expect(input.type).toBe('text')
});
});

@ -0,0 +1,27 @@
import { InjectionToken, inject } from '@angular/core';
import { LocalizationService } from '../services';
export const SORT_COMPARE_FUNC = new InjectionToken< 0 | 1 | -1 >('SORT_COMPARE_FUNC');
export function compareFuncFactory() {
const localizationService = inject(LocalizationService)
const fn = (a,b) => {
const aName = localizationService.instant(a.name);
const bName = localizationService.instant(b.name);
const aNumber = a.order;
const bNumber = b.order;
if (!Number.isInteger(aNumber)) return 1;
if (!Number.isInteger(bNumber)) return -1;
if (aNumber > bNumber) return 1
if (aNumber < bNumber) return -1
if ( aName > bName ) return 1;
if ( aName < bName ) return -1;
return 0
}
return fn
}

@ -17,7 +17,7 @@ export function configureSettingTabs(settingtabs: SettingTabsService) {
settingtabs.add([
{
name: eFeatureManagementTabNames.FeatureManagement,
order: 104,
order: 100,
requiredPolicy: 'FeatureManagement.ManageHostFeatures',
component: FeatureManagementTabComponent,
},

@ -7,7 +7,7 @@
</ng-container>
</ng-template>
<div [ngClass]="containerClassName" class="mb-3">
<div [ngClass]="containerClassName" class="mb-2">
<ng-template ngSwitchCase="input">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<input

@ -61,7 +61,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
asterisk = '';
containerClassName = 'mb-3';
containerClassName = 'mb-2';
options$: Observable<ABP.Option<any>[]> = of([]);

@ -1,8 +1,17 @@
import { Injector, Type } from '@angular/core';
import { Observable, of } from 'rxjs';
export interface Badge {
count?: number | Observable<number>;
color?: string;
icon?: string;
}
export class NavItem {
id?: string | number;
name?: string;
description?: string;
badge?: Badge;
component?: Type<any>;
html?: string;
action?: () => void;

Loading…
Cancel
Save