mirror of https://github.com/abpframework/abp.git
314 changed files with 528296 additions and 1676 deletions
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
@ -0,0 +1,78 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using Volo.Abp.Cli.Commands; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps |
||||
|
{ |
||||
|
public class MicroserviceServiceRandomPortStep : ProjectBuildPipelineStep |
||||
|
{ |
||||
|
private readonly string _defaultPort = string.Empty; |
||||
|
private string _tyeFileContent = null; |
||||
|
|
||||
|
public MicroserviceServiceRandomPortStep(string defaultPort) |
||||
|
{ |
||||
|
_defaultPort = defaultPort; |
||||
|
} |
||||
|
|
||||
|
public override void Execute(ProjectBuildContext context) |
||||
|
{ |
||||
|
var newPort = GetNewRandomPort(context); |
||||
|
|
||||
|
var targetFiles = context.Files.Where(f=> f.Name.EndsWith("launchSettings.json") || f.Name.EndsWith("appsettings.json")).ToList(); |
||||
|
|
||||
|
foreach (var file in targetFiles) |
||||
|
{ |
||||
|
file.SetContent(file.Content.Replace(_defaultPort, newPort)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private string GetNewRandomPort(ProjectBuildContext context) |
||||
|
{ |
||||
|
string newPort; |
||||
|
var rnd = new Random(); |
||||
|
var tryCount = 0; |
||||
|
|
||||
|
do |
||||
|
{ |
||||
|
newPort = rnd.Next(44350, 45350).ToString(); |
||||
|
|
||||
|
if (tryCount++ > 2000) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
} while (PortExistsForAnotherService(context, newPort)); |
||||
|
|
||||
|
return newPort; |
||||
|
} |
||||
|
|
||||
|
private bool PortExistsForAnotherService(ProjectBuildContext context, string newPort) |
||||
|
{ |
||||
|
return ReadTyeFileContent(context).SplitToLines().Any(l => l.Contains("port") && l.Contains(newPort)); |
||||
|
} |
||||
|
|
||||
|
private string ReadTyeFileContent(ProjectBuildContext context) |
||||
|
{ |
||||
|
if (_tyeFileContent != null) |
||||
|
{ |
||||
|
return _tyeFileContent; |
||||
|
} |
||||
|
|
||||
|
var solutionFolderPath = context.BuildArgs.ExtraProperties[NewCommand.Options.OutputFolder.Short] ?? |
||||
|
context.BuildArgs.ExtraProperties[NewCommand.Options.OutputFolder.Long] ?? |
||||
|
Directory.GetCurrentDirectory(); |
||||
|
|
||||
|
var tyeFilePath = Path.Combine(solutionFolderPath, "tye.yaml"); |
||||
|
|
||||
|
if (!File.Exists(tyeFilePath)) |
||||
|
{ |
||||
|
return String.Empty; |
||||
|
} |
||||
|
|
||||
|
_tyeFileContent = File.ReadAllText(tyeFilePath); |
||||
|
|
||||
|
return _tyeFileContent; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps |
||||
|
{ |
||||
|
public class RemoveProjectFromTyeStep : ProjectBuildPipelineStep |
||||
|
{ |
||||
|
private readonly string _name; |
||||
|
|
||||
|
public RemoveProjectFromTyeStep(string name) |
||||
|
{ |
||||
|
_name = name; |
||||
|
} |
||||
|
|
||||
|
public override void Execute(ProjectBuildContext context) |
||||
|
{ |
||||
|
var tyeFile = context.Files.FirstOrDefault(f => f.Name == "/tye.yaml"); |
||||
|
|
||||
|
if (tyeFile == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var lines = tyeFile.GetLines(); |
||||
|
var newLines = new List<string>(); |
||||
|
|
||||
|
var nameLine = $"- name:"; |
||||
|
var isOneOfTargetLines = false; |
||||
|
|
||||
|
foreach (var line in lines) |
||||
|
{ |
||||
|
if (line.Equals($"{nameLine} {_name}")) |
||||
|
{ |
||||
|
isOneOfTargetLines = true; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (line.StartsWith(nameLine)) |
||||
|
{ |
||||
|
isOneOfTargetLines = false; |
||||
|
} |
||||
|
|
||||
|
if (!isOneOfTargetLines) |
||||
|
{ |
||||
|
newLines.Add(line); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
tyeFile.SetContent(String.Join(Environment.NewLine, newLines)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Volo.Abp.Logging; |
||||
|
|
||||
|
namespace Microsoft.Extensions.DependencyInjection |
||||
|
{ |
||||
|
public static class ServiceCollectionLoggingExtensions |
||||
|
{ |
||||
|
public static ILogger<T> GetInitLogger<T>(this IServiceCollection services) |
||||
|
{ |
||||
|
return services.GetSingletonInstance<IInitLoggerFactory>().Create<T>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
|
||||
|
namespace Volo.Abp.Logging |
||||
|
{ |
||||
|
public class AbpInitLogEntry |
||||
|
{ |
||||
|
public LogLevel LogLevel { get; set; } |
||||
|
|
||||
|
public EventId EventId { get; set; } |
||||
|
|
||||
|
public object State { get; set; } |
||||
|
|
||||
|
public Exception Exception { get; set; } |
||||
|
|
||||
|
public Func<object, Exception, string> Formatter { get; set; } |
||||
|
|
||||
|
public string Message => Formatter(State, Exception); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
|
||||
|
namespace Volo.Abp.Logging |
||||
|
{ |
||||
|
public class DefaultInitLogger<T> : IInitLogger<T> |
||||
|
{ |
||||
|
public List<AbpInitLogEntry> Entries { get; } |
||||
|
|
||||
|
public DefaultInitLogger() |
||||
|
{ |
||||
|
Entries = new List<AbpInitLogEntry>(); |
||||
|
} |
||||
|
|
||||
|
public virtual void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) |
||||
|
{ |
||||
|
Entries.Add(new AbpInitLogEntry |
||||
|
{ |
||||
|
LogLevel = logLevel, |
||||
|
EventId = eventId, |
||||
|
State = state, |
||||
|
Exception = exception, |
||||
|
Formatter = (s, e) => formatter((TState)s, e), |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public virtual bool IsEnabled(LogLevel logLevel) |
||||
|
{ |
||||
|
return logLevel != LogLevel.None; |
||||
|
} |
||||
|
|
||||
|
public virtual IDisposable BeginScope<TState>(TState state) |
||||
|
{ |
||||
|
return NullDisposable.Instance; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Logging |
||||
|
{ |
||||
|
public class DefaultInitLoggerFactory : IInitLoggerFactory |
||||
|
{ |
||||
|
private readonly Dictionary<Type, object> _cache = new Dictionary<Type, object>(); |
||||
|
|
||||
|
public virtual IInitLogger<T> Create<T>() |
||||
|
{ |
||||
|
return (IInitLogger<T>)_cache.GetOrAdd(typeof(T), () => new DefaultInitLogger<T>());; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
|
||||
|
namespace Volo.Abp.Logging |
||||
|
{ |
||||
|
public interface IInitLogger<out T> : ILogger<T> |
||||
|
{ |
||||
|
public List<AbpInitLogEntry> Entries { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.Logging |
||||
|
{ |
||||
|
public interface IInitLoggerFactory |
||||
|
{ |
||||
|
IInitLogger<T> Create<T>(); |
||||
|
} |
||||
|
} |
||||
@ -1,21 +1,23 @@ |
|||||
using System; |
using System; |
||||
using System.Linq; |
using System.Linq; |
||||
using JetBrains.Annotations; |
using JetBrains.Annotations; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Volo.Abp.Logging; |
||||
|
|
||||
namespace Volo.Abp.Modularity.PlugIns |
namespace Volo.Abp.Modularity.PlugIns |
||||
{ |
{ |
||||
public static class PlugInSourceExtensions |
public static class PlugInSourceExtensions |
||||
{ |
{ |
||||
[NotNull] |
[NotNull] |
||||
public static Type[] GetModulesWithAllDependencies([NotNull] this IPlugInSource plugInSource) |
public static Type[] GetModulesWithAllDependencies([NotNull] this IPlugInSource plugInSource, ILogger logger) |
||||
{ |
{ |
||||
Check.NotNull(plugInSource, nameof(plugInSource)); |
Check.NotNull(plugInSource, nameof(plugInSource)); |
||||
|
|
||||
return plugInSource |
return plugInSource |
||||
.GetModules() |
.GetModules() |
||||
.SelectMany(AbpModuleHelper.FindAllModuleTypes) |
.SelectMany(type => AbpModuleHelper.FindAllModuleTypes(type, logger)) |
||||
.Distinct() |
.Distinct() |
||||
.ToArray(); |
.ToArray(); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
File diff suppressed because it is too large
@ -0,0 +1,56 @@ |
|||||
|
using System; |
||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
|
||||
|
namespace Volo.CmsKit.Migrations |
||||
|
{ |
||||
|
public partial class BlogPost_CoverImage : Migration |
||||
|
{ |
||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.DropTable( |
||||
|
name: "CmsContents"); |
||||
|
|
||||
|
migrationBuilder.AddColumn<Guid>( |
||||
|
name: "CoverImageMediaId", |
||||
|
table: "CmsBlogPosts", |
||||
|
type: "uniqueidentifier", |
||||
|
nullable: true); |
||||
|
} |
||||
|
|
||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.DropColumn( |
||||
|
name: "CoverImageMediaId", |
||||
|
table: "CmsBlogPosts"); |
||||
|
|
||||
|
migrationBuilder.CreateTable( |
||||
|
name: "CmsContents", |
||||
|
columns: table => new |
||||
|
{ |
||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), |
||||
|
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true), |
||||
|
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false), |
||||
|
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), |
||||
|
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), |
||||
|
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true), |
||||
|
EntityId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false), |
||||
|
EntityType = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false), |
||||
|
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true), |
||||
|
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false), |
||||
|
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true), |
||||
|
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), |
||||
|
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), |
||||
|
Value = table.Column<string>(type: "nvarchar(max)", maxLength: 2147483647, nullable: false) |
||||
|
}, |
||||
|
constraints: table => |
||||
|
{ |
||||
|
table.PrimaryKey("PK_CmsContents", x => x.Id); |
||||
|
}); |
||||
|
|
||||
|
migrationBuilder.CreateIndex( |
||||
|
name: "IX_CmsContents_TenantId_EntityType_EntityId", |
||||
|
table: "CmsContents", |
||||
|
columns: new[] { "TenantId", "EntityType", "EntityId" }); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
/* |
||||
|
https://jquery.com/upgrade-guide/3.5/#jquery-htmlprefilter-changes
|
||||
|
*/ |
||||
|
var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi; |
||||
|
jQuery.htmlPrefilter = function (html) { |
||||
|
return html.replace(rxhtmlTag, "<$1></$2>"); |
||||
|
}; |
||||
@ -1,22 +1,124 @@ |
|||||
{ |
{ |
||||
"culture": "zh-Hans", |
"culture": "zh-Hans", |
||||
"texts": { |
"texts": { |
||||
"PickYourReaction": "选择你的回应", |
"BlogDeletionConfirmationMessage": "博客 '{0}' 将被删除. 你确定吗?", |
||||
"YourComment": "你的评论", |
"BlogFeatureNotAvailable": "这个功能目前不可用. 使用 `GlobalFeatureManager` 来启用它.", |
||||
"YourReply": "你的回复", |
"BlogId": "博客", |
||||
|
"BlogPostDeletionConfirmationMessage": "博客帖子 '{0}' 将被删除. 你确定吗?", |
||||
|
"BlogPosts": "博客帖子", |
||||
|
"Blogs": "博客", |
||||
|
"ChoosePreference": "选择首选项...", |
||||
|
"Cms": "Cms", |
||||
|
"CmsKit.Comments": "评论", |
||||
|
"CmsKit.Ratings": "评分", |
||||
|
"CmsKit.Reactions": "反应", |
||||
|
"CmsKit.Tags": "标签", |
||||
|
"CmsKit:0002": "内容已经存在!", |
||||
|
"CmsKit:0003": "实体 {0} 不可标记.", |
||||
|
"CmsKit:Blog:0001": "给定的slug ({Slug}) 已经存在!", |
||||
|
"CmsKit:BlogPost:0001": "给定的slug已经存在!", |
||||
|
"CmsKit:Comments:0001": "实体不可 {0} 不可评论.", |
||||
|
"CmsKit:Media:0001": "'{Name}' 不是有效的媒体名称.", |
||||
|
"CmsKit:Media:0002": "实体不可以含有媒体", |
||||
|
"CmsKit:Page:0001": "给定的url ({0}) 已经存在.", |
||||
|
"CmsKit:Tag:0002": "实体不可标记!", |
||||
|
"CommentAuthorizationExceptionMessage": "这些评论不允许公开显示", |
||||
|
"CommentDeletionConfirmationMessage": "此评论和所有回复将被删除!", |
||||
"Comments": "评论", |
"Comments": "评论", |
||||
"Send": "发送", |
"ContentDeletionConfirmationMessage": "你确定要删除这个内容吗?", |
||||
|
"Contents": "内容", |
||||
|
"CoverImage": "封面图片", |
||||
|
"CreateBlogPostPage": "新博客帖子", |
||||
|
"CreationTime": "创建时间", |
||||
"Delete": "删除", |
"Delete": "删除", |
||||
"Reply": "回复", |
"Detail": "详情", |
||||
"Update": "更新", |
"Details": "详情", |
||||
|
"DoYouPreferAdditionalEmails": "你是否更喜欢额外的邮件?", |
||||
"Edit": "修改", |
"Edit": "修改", |
||||
|
"EndDate": "结束时间", |
||||
|
"EntityId": "实体Id", |
||||
|
"EntityType": "实体类型", |
||||
|
"ExportCSV": "导出CSV", |
||||
|
"Features": "功能", |
||||
|
"GenericDeletionConfirmationMessage": "你确定删除 '{0}' 吗?", |
||||
|
"LastModification": "最后一次修改", |
||||
"LoginToAddComment": "登录添加评论", |
"LoginToAddComment": "登录添加评论", |
||||
|
"LoginToRate": "登录进行评分", |
||||
"LoginToReply": "登录进行回复", |
"LoginToReply": "登录进行回复", |
||||
|
"Menu:CMS": "CMS", |
||||
|
"Message": "消息", |
||||
"MessageDeletionConfirmationMessage": "这条评论将被完全删除", |
"MessageDeletionConfirmationMessage": "这条评论将被完全删除", |
||||
"CommentAuthorizationExceptionMessage": "这些评论不允许公开显示", |
"Name": "名称", |
||||
"Undo": "撤消", |
"New": "新", |
||||
|
"OK": "OK", |
||||
|
"PageDeletionConfirmationMessage": "你确定删除这个页面吗?", |
||||
|
"PageSlugInformation": "Slug用于url. 你的url将是 '/pages/{{slug}}'.", |
||||
|
"Permission:BlogManagement": "博客管理", |
||||
|
"Permission:BlogManagement.Create": "创建", |
||||
|
"Permission:BlogManagement.Delete": "删除", |
||||
|
"Permission:BlogManagement.Features": "删除", |
||||
|
"Permission:BlogManagement.Update": "更新", |
||||
|
"Permission:BlogPostManagement": "博客帖子管理", |
||||
|
"Permission:BlogPostManagement.Create": "创建", |
||||
|
"Permission:BlogPostManagement.Delete": "删除", |
||||
|
"Permission:BlogPostManagement.Update": "更新", |
||||
|
"Permission:CmsKit": "Cms工具包", |
||||
|
"Permission:Comments": "评论管理", |
||||
|
"Permission:Comments.Delete": "删除", |
||||
|
"Permission:Contents": "内容管理", |
||||
|
"Permission:Contents.Create": "创建内容", |
||||
|
"Permission:Contents.Delete": "删除内容", |
||||
|
"Permission:Contents.Update": "更新内容", |
||||
|
"Permission:MediaDescriptorManagement": "媒体管理", |
||||
|
"Permission:MediaDescriptorManagement:Create": "创建", |
||||
|
"Permission:MediaDescriptorManagement:Delete": "删除", |
||||
|
"Permission:PageManagement": "页面管理", |
||||
|
"Permission:PageManagement:Create": "创建", |
||||
|
"Permission:PageManagement:Delete": "删除", |
||||
|
"Permission:PageManagement:Update": "更新", |
||||
|
"Permission:TagManagement": "标签管理", |
||||
|
"Permission:TagManagement.Create": "创建", |
||||
|
"Permission:TagManagement.Delete": "删除", |
||||
|
"Permission:TagManagement.Update": "更新", |
||||
|
"PickYourReaction": "选择你的回应", |
||||
"RatingUndoMessage": "您的评分将被撤消", |
"RatingUndoMessage": "您的评分将被撤消", |
||||
"LoginToRate": "登录进行评分", |
"Read": "阅读", |
||||
"Star": "星" |
"RepliesToThisComment": "回复此评论", |
||||
|
"Reply": "回复", |
||||
|
"ReplyTo": "回复", |
||||
|
"SamplePageMessage": "Pro模块的示例页面", |
||||
|
"SaveChanges": "保存更改", |
||||
|
"SelectAll": "选择所有", |
||||
|
"Send": "发送", |
||||
|
"SendMessage": "发送消息", |
||||
|
"ShortDescription": "简介", |
||||
|
"Slug": "Slug", |
||||
|
"Source": "源", |
||||
|
"SourceUrl": "源URL", |
||||
|
"Star": "星", |
||||
|
"StartDate": "开始时间", |
||||
|
"Subject": "主题", |
||||
|
"SubjectPlaceholder": "请输入主题", |
||||
|
"Submit": "提交", |
||||
|
"Subscribe": "订阅", |
||||
|
"SuccessfullyDeleted": "删除成功!", |
||||
|
"SuccessfullySaved": "保存成功!", |
||||
|
"TagDeletionConfirmationMessage": "你确定删除 '{0}' 标签吗?", |
||||
|
"Tags": "标签", |
||||
|
"Text": "文本", |
||||
|
"ThankYou": "谢谢你", |
||||
|
"Title": "标题", |
||||
|
"Undo": "撤消", |
||||
|
"Update": "更新", |
||||
|
"UpdatePreferenceSuccessMessage": "您的首选项已经保存", |
||||
|
"UpdateYourEmailPreferences": "更新你的邮件首选项", |
||||
|
"UploadFailedMessage": "上传失败", |
||||
|
"UserId": "用户Id", |
||||
|
"Username": "用户名称", |
||||
|
"YourComment": "你的评论", |
||||
|
"YourEmailAddress": "你的邮件地址", |
||||
|
"YourFullName": "你的全称", |
||||
|
"YourMessage": "你的消息", |
||||
|
"YourReply": "你的回复" |
||||
} |
} |
||||
} |
} |
||||
@ -1,9 +0,0 @@ |
|||||
using Volo.Abp.BlobStoring; |
|
||||
|
|
||||
namespace Volo.CmsKit.Blogs |
|
||||
{ |
|
||||
[BlobContainerName("blog-post-cover-images")] |
|
||||
public class BlogPostCoverImageContainer |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Volo.Abp.Identity:PasswordTooShort": "密码长度必须大于{0}字符. ", |
||||
|
"Volo.Abp.Identity:PasswordRequiresNonAlphanumeric": "密码必须至少包含一个非字母数字字符." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.IdentityServer |
||||
|
{ |
||||
|
public class AbpClaimsServiceOptions |
||||
|
{ |
||||
|
public List<string> RequestedClaims { get; } |
||||
|
|
||||
|
public AbpClaimsServiceOptions() |
||||
|
{ |
||||
|
RequestedClaims = new List<string>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<h1> @abp/ng.account </h1> |
||||
|
|
||||
|
[docs.abp.io](https://docs.abp.io) |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", |
||||
|
"dest": "../../dist/account/config", |
||||
|
"lib": { |
||||
|
"entryFile": "src/public-api.ts" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { ModuleWithProviders, NgModule } from '@angular/core'; |
||||
|
import { ACCOUNT_ROUTE_PROVIDERS } from './providers/route.provider'; |
||||
|
|
||||
|
@NgModule() |
||||
|
export class AccountConfigModule { |
||||
|
static forRoot(): ModuleWithProviders<AccountConfigModule> { |
||||
|
return { |
||||
|
ngModule: AccountConfigModule, |
||||
|
providers: [ACCOUNT_ROUTE_PROVIDERS], |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
export * from './route-names'; |
||||
@ -0,0 +1,6 @@ |
|||||
|
export const enum eAccountRouteNames { |
||||
|
Account = 'AbpAccount::Menu:Account', |
||||
|
Login = 'AbpAccount::Login', |
||||
|
Register = 'AbpAccount::Register', |
||||
|
ManageProfile = 'AbpAccount::ManageYourProfile', |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
export * from './route.provider'; |
||||
@ -0,0 +1,39 @@ |
|||||
|
import { eLayoutType, RoutesService } from '@abp/ng.core'; |
||||
|
import { APP_INITIALIZER } from '@angular/core'; |
||||
|
import { eAccountRouteNames } from '../enums/route-names'; |
||||
|
|
||||
|
export const ACCOUNT_ROUTE_PROVIDERS = [ |
||||
|
{ provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true }, |
||||
|
]; |
||||
|
|
||||
|
export function configureRoutes(routes: RoutesService) { |
||||
|
return () => { |
||||
|
routes.add([ |
||||
|
{ |
||||
|
path: '/account', |
||||
|
name: eAccountRouteNames.Account, |
||||
|
invisible: true, |
||||
|
layout: eLayoutType.application, |
||||
|
order: 1, |
||||
|
}, |
||||
|
{ |
||||
|
path: '/account/login', |
||||
|
name: eAccountRouteNames.Login, |
||||
|
parentName: eAccountRouteNames.Account, |
||||
|
order: 1, |
||||
|
}, |
||||
|
{ |
||||
|
path: '/account/register', |
||||
|
name: eAccountRouteNames.Register, |
||||
|
parentName: eAccountRouteNames.Account, |
||||
|
order: 2, |
||||
|
}, |
||||
|
{ |
||||
|
path: '/account/manage-profile', |
||||
|
name: eAccountRouteNames.ManageProfile, |
||||
|
parentName: eAccountRouteNames.Account, |
||||
|
order: 3, |
||||
|
}, |
||||
|
]); |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
export * from './account-config.module'; |
||||
|
export * from './enums'; |
||||
|
export * from './providers'; |
||||
@ -0,0 +1,6 @@ |
|||||
|
const jestConfig = require('../../jest.config'); |
||||
|
|
||||
|
module.exports = { |
||||
|
...jestConfig, |
||||
|
name: 'account', |
||||
|
}; |
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json", |
||||
|
"dest": "../../dist/account", |
||||
|
"lib": { |
||||
|
"entryFile": "src/public-api.ts" |
||||
|
}, |
||||
|
"whitelistedNonPeerDependencies": ["@abp/ng.theme.shared"] |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
module.exports = { |
||||
|
entryPoints: { |
||||
|
'.': {}, |
||||
|
'./config': {} |
||||
|
}, |
||||
|
}; |
||||
@ -0,0 +1,16 @@ |
|||||
|
{ |
||||
|
"name": "@abp/ng.account", |
||||
|
"version": "4.3.0", |
||||
|
"homepage": "https://abp.io", |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "https://github.com/abpframework/abp.git" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"@abp/ng.theme.shared": "~3.3.2", |
||||
|
"tslib": "^2.0.0" |
||||
|
}, |
||||
|
"publishConfig": { |
||||
|
"access": "public" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
import { |
||||
|
AuthGuard, |
||||
|
DynamicLayoutComponent, |
||||
|
ReplaceableComponents, |
||||
|
ReplaceableRouteContainerComponent, |
||||
|
} from '@abp/ng.core'; |
||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { RouterModule, Routes } from '@angular/router'; |
||||
|
import { AuthWrapperComponent } from './components/auth-wrapper/auth-wrapper.component'; |
||||
|
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component'; |
||||
|
import { LoginComponent } from './components/login/login.component'; |
||||
|
import { ManageProfileComponent } from './components/manage-profile/manage-profile.component'; |
||||
|
import { RegisterComponent } from './components/register/register.component'; |
||||
|
import { ResetPasswordComponent } from './components/reset-password/reset-password.component'; |
||||
|
import { eAccountComponents } from './enums/components'; |
||||
|
import { AuthenticationFlowGuard } from './guards/authentication-flow.guard'; |
||||
|
|
||||
|
const routes: Routes = [ |
||||
|
{ path: '', pathMatch: 'full', redirectTo: 'login' }, |
||||
|
{ |
||||
|
path: '', |
||||
|
component: DynamicLayoutComponent, |
||||
|
children: [ |
||||
|
{ |
||||
|
path: '', |
||||
|
component: AuthWrapperComponent, |
||||
|
children: [ |
||||
|
{ |
||||
|
path: 'login', |
||||
|
component: ReplaceableRouteContainerComponent, |
||||
|
canActivate: [AuthenticationFlowGuard], |
||||
|
data: { |
||||
|
replaceableComponent: { |
||||
|
key: eAccountComponents.Login, |
||||
|
defaultComponent: LoginComponent, |
||||
|
} as ReplaceableComponents.RouteData<LoginComponent>, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
path: 'register', |
||||
|
component: ReplaceableRouteContainerComponent, |
||||
|
canActivate: [AuthenticationFlowGuard], |
||||
|
data: { |
||||
|
replaceableComponent: { |
||||
|
key: eAccountComponents.Register, |
||||
|
defaultComponent: RegisterComponent, |
||||
|
} as ReplaceableComponents.RouteData<RegisterComponent>, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
path: 'forgot-password', |
||||
|
component: ReplaceableRouteContainerComponent, |
||||
|
canActivate: [AuthenticationFlowGuard], |
||||
|
data: { |
||||
|
replaceableComponent: { |
||||
|
key: eAccountComponents.ForgotPassword, |
||||
|
defaultComponent: ForgotPasswordComponent, |
||||
|
} as ReplaceableComponents.RouteData<ForgotPasswordComponent>, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
path: 'reset-password', |
||||
|
component: ReplaceableRouteContainerComponent, |
||||
|
canActivate: [AuthenticationFlowGuard], |
||||
|
data: { |
||||
|
replaceableComponent: { |
||||
|
key: eAccountComponents.ResetPassword, |
||||
|
defaultComponent: ResetPasswordComponent, |
||||
|
} as ReplaceableComponents.RouteData<ResetPasswordComponent>, |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
path: 'manage-profile', |
||||
|
component: ReplaceableRouteContainerComponent, |
||||
|
canActivate: [AuthGuard], |
||||
|
data: { |
||||
|
replaceableComponent: { |
||||
|
key: eAccountComponents.ManageProfile, |
||||
|
defaultComponent: ManageProfileComponent, |
||||
|
} as ReplaceableComponents.RouteData<ManageProfileComponent>, |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
@NgModule({ |
||||
|
imports: [RouterModule.forChild(routes)], |
||||
|
exports: [RouterModule], |
||||
|
}) |
||||
|
export class AccountRoutingModule {} |
||||
@ -0,0 +1,63 @@ |
|||||
|
import { CoreModule, LazyModuleFactory } from '@abp/ng.core'; |
||||
|
import { ThemeSharedModule } from '@abp/ng.theme.shared'; |
||||
|
import { ModuleWithProviders, NgModule, NgModuleFactory } from '@angular/core'; |
||||
|
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; |
||||
|
import { NgxValidateCoreModule } from '@ngx-validate/core'; |
||||
|
import { AccountRoutingModule } from './account-routing.module'; |
||||
|
import { AuthWrapperComponent } from './components/auth-wrapper/auth-wrapper.component'; |
||||
|
import { ChangePasswordComponent } from './components/change-password/change-password.component'; |
||||
|
import { LoginComponent } from './components/login/login.component'; |
||||
|
import { ManageProfileComponent } from './components/manage-profile/manage-profile.component'; |
||||
|
import { PersonalSettingsComponent } from './components/personal-settings/personal-settings.component'; |
||||
|
import { RegisterComponent } from './components/register/register.component'; |
||||
|
import { TenantBoxComponent } from './components/tenant-box/tenant-box.component'; |
||||
|
import { AccountConfigOptions } from './models/config-options'; |
||||
|
import { ACCOUNT_CONFIG_OPTIONS } from './tokens/config-options.token'; |
||||
|
import { accountConfigOptionsFactory } from './utils/factory-utils'; |
||||
|
import { AuthenticationFlowGuard } from './guards/authentication-flow.guard'; |
||||
|
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component'; |
||||
|
import { ResetPasswordComponent } from './components/reset-password/reset-password.component'; |
||||
|
|
||||
|
const declarations = [ |
||||
|
AuthWrapperComponent, |
||||
|
LoginComponent, |
||||
|
RegisterComponent, |
||||
|
TenantBoxComponent, |
||||
|
ChangePasswordComponent, |
||||
|
ManageProfileComponent, |
||||
|
PersonalSettingsComponent, |
||||
|
ForgotPasswordComponent, |
||||
|
ResetPasswordComponent, |
||||
|
]; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [...declarations], |
||||
|
imports: [ |
||||
|
CoreModule, |
||||
|
AccountRoutingModule, |
||||
|
ThemeSharedModule, |
||||
|
NgbDropdownModule, |
||||
|
NgxValidateCoreModule, |
||||
|
], |
||||
|
exports: [...declarations], |
||||
|
}) |
||||
|
export class AccountModule { |
||||
|
static forChild(options = {} as AccountConfigOptions): ModuleWithProviders<AccountModule> { |
||||
|
return { |
||||
|
ngModule: AccountModule, |
||||
|
providers: [ |
||||
|
AuthenticationFlowGuard, |
||||
|
{ provide: ACCOUNT_CONFIG_OPTIONS, useValue: options }, |
||||
|
{ |
||||
|
provide: 'ACCOUNT_OPTIONS', |
||||
|
useFactory: accountConfigOptionsFactory, |
||||
|
deps: [ACCOUNT_CONFIG_OPTIONS], |
||||
|
}, |
||||
|
], |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
static forLazy(options = {} as AccountConfigOptions): NgModuleFactory<AccountModule> { |
||||
|
return new LazyModuleFactory(AccountModule.forChild(options)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
<div class="row"> |
||||
|
<div class="mx-auto col col-md-5"> |
||||
|
<ng-container *ngIf="(isMultiTenancyEnabled$ | async) && multiTenancy.isTenantBoxVisible"> |
||||
|
<abp-tenant-box *abpReplaceableTemplate="{ componentKey: tenantBoxKey }"></abp-tenant-box> |
||||
|
</ng-container> |
||||
|
|
||||
|
<div class="abp-account-container"> |
||||
|
<div |
||||
|
*ngIf="enableLocalLogin$ | async; else disableLocalLoginTemplate" |
||||
|
class="card mt-3 shadow-sm rounded" |
||||
|
> |
||||
|
<div class="card-body p-5"> |
||||
|
<router-outlet></router-outlet> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<ng-template #disableLocalLoginTemplate> |
||||
|
<div class="alert alert-warning"> |
||||
|
<strong>{{ 'AbpAccount::InvalidLoginRequest' | abpLocalization }}</strong> |
||||
|
{{ 'AbpAccount::ThereAreNoLoginSchemesConfiguredForThisClient' | abpLocalization }} |
||||
|
</div> |
||||
|
</ng-template> |
||||
@ -0,0 +1,28 @@ |
|||||
|
import { ConfigStateService, MultiTenancyService, SubscriptionService } from '@abp/ng.core'; |
||||
|
import { Component } from '@angular/core'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
import { map } from 'rxjs/operators'; |
||||
|
import { eAccountComponents } from '../../enums/components'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'abp-auth-wrapper', |
||||
|
templateUrl: './auth-wrapper.component.html', |
||||
|
exportAs: 'abpAuthWrapper', |
||||
|
providers: [SubscriptionService], |
||||
|
}) |
||||
|
export class AuthWrapperComponent { |
||||
|
isMultiTenancyEnabled$ = this.configState.getDeep$('multiTenancy.isEnabled'); |
||||
|
|
||||
|
get enableLocalLogin$(): Observable<boolean> { |
||||
|
return this.configState |
||||
|
.getSetting$('Abp.Account.EnableLocalLogin') |
||||
|
.pipe(map(value => value?.toLowerCase() !== 'false')); |
||||
|
} |
||||
|
|
||||
|
tenantBoxKey = eAccountComponents.TenantBox; |
||||
|
|
||||
|
constructor( |
||||
|
public readonly multiTenancy: MultiTenancyService, |
||||
|
private configState: ConfigStateService, |
||||
|
) {} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
<form [formGroup]="form" (ngSubmit)="onSubmit()" [mapErrorsFn]="mapErrorsFn" validateOnSubmit> |
||||
|
<div *ngIf="!hideCurrentPassword" class="form-group"> |
||||
|
<label for="current-password">{{ |
||||
|
'AbpIdentity::DisplayName:CurrentPassword' | abpLocalization |
||||
|
}}</label |
||||
|
><span> * </span |
||||
|
><input |
||||
|
type="password" |
||||
|
id="current-password" |
||||
|
class="form-control" |
||||
|
formControlName="password" |
||||
|
autofocus |
||||
|
autocomplete="current-password" |
||||
|
/> |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<label for="new-password">{{ 'AbpIdentity::DisplayName:NewPassword' | abpLocalization }}</label |
||||
|
><span> * </span |
||||
|
><input |
||||
|
type="password" |
||||
|
id="new-password" |
||||
|
class="form-control" |
||||
|
formControlName="newPassword" |
||||
|
autocomplete="new-password" |
||||
|
/> |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<label for="confirm-new-password">{{ |
||||
|
'AbpIdentity::DisplayName:NewPasswordConfirm' | abpLocalization |
||||
|
}}</label |
||||
|
><span> * </span |
||||
|
><input |
||||
|
type="password" |
||||
|
id="confirm-new-password" |
||||
|
class="form-control" |
||||
|
formControlName="repeatNewPassword" |
||||
|
autocomplete="new-password" |
||||
|
/> |
||||
|
</div> |
||||
|
<abp-button |
||||
|
iconClass="fa fa-check" |
||||
|
buttonClass="btn btn-primary color-white" |
||||
|
buttonType="submit" |
||||
|
[loading]="inProgress" |
||||
|
[disabled]="form?.invalid" |
||||
|
>{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button |
||||
|
> |
||||
|
</form> |
||||
@ -0,0 +1,99 @@ |
|||||
|
import { Profile, ProfileService } from '@abp/ng.core'; |
||||
|
import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared'; |
||||
|
import { Component, Injector, Input, OnInit } from '@angular/core'; |
||||
|
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; |
||||
|
import { comparePasswords, Validation } from '@ngx-validate/core'; |
||||
|
import { finalize } from 'rxjs/operators'; |
||||
|
import snq from 'snq'; |
||||
|
import { Account } from '../../models/account'; |
||||
|
import { ManageProfileStateService } from '../../services/manage-profile.state.service'; |
||||
|
|
||||
|
const { required } = Validators; |
||||
|
|
||||
|
const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword']; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'abp-change-password-form', |
||||
|
templateUrl: './change-password.component.html', |
||||
|
exportAs: 'abpChangePasswordForm', |
||||
|
}) |
||||
|
export class ChangePasswordComponent |
||||
|
implements OnInit, Account.ChangePasswordComponentInputs, Account.ChangePasswordComponentOutputs { |
||||
|
form: FormGroup; |
||||
|
|
||||
|
inProgress: boolean; |
||||
|
|
||||
|
hideCurrentPassword: boolean; |
||||
|
|
||||
|
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => { |
||||
|
if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors; |
||||
|
|
||||
|
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch')); |
||||
|
}; |
||||
|
|
||||
|
constructor( |
||||
|
private fb: FormBuilder, |
||||
|
private injector: Injector, |
||||
|
private toasterService: ToasterService, |
||||
|
private profileService: ProfileService, |
||||
|
private manageProfileState: ManageProfileStateService, |
||||
|
) {} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.hideCurrentPassword = !this.manageProfileState.getProfile()?.hasPassword; |
||||
|
|
||||
|
const passwordValidations = getPasswordValidators(this.injector); |
||||
|
|
||||
|
this.form = this.fb.group( |
||||
|
{ |
||||
|
password: ['', required], |
||||
|
newPassword: [ |
||||
|
'', |
||||
|
{ |
||||
|
validators: [required, ...passwordValidations], |
||||
|
}, |
||||
|
], |
||||
|
repeatNewPassword: [ |
||||
|
'', |
||||
|
{ |
||||
|
validators: [required, ...passwordValidations], |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
validators: [comparePasswords(PASSWORD_FIELDS)], |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
if (this.hideCurrentPassword) this.form.removeControl('password'); |
||||
|
} |
||||
|
|
||||
|
onSubmit() { |
||||
|
if (this.form.invalid) return; |
||||
|
this.inProgress = true; |
||||
|
this.profileService |
||||
|
.changePassword({ |
||||
|
...(!this.hideCurrentPassword && { currentPassword: this.form.get('password').value }), |
||||
|
newPassword: this.form.get('newPassword').value, |
||||
|
}) |
||||
|
.pipe(finalize(() => (this.inProgress = false))) |
||||
|
.subscribe({ |
||||
|
next: () => { |
||||
|
this.form.reset(); |
||||
|
this.toasterService.success('AbpAccount::PasswordChangedMessage', '', { |
||||
|
life: 5000, |
||||
|
}); |
||||
|
|
||||
|
if (this.hideCurrentPassword) { |
||||
|
this.hideCurrentPassword = false; |
||||
|
this.form.addControl('password', new FormControl('', [required])); |
||||
|
} |
||||
|
}, |
||||
|
error: err => { |
||||
|
this.toasterService.error( |
||||
|
snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'), |
||||
|
); |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
<h4>{{ 'AbpAccount::ForgotPassword' | abpLocalization }}</h4> |
||||
|
|
||||
|
<form |
||||
|
*ngIf="!isEmailSent; else emailSentTemplate" |
||||
|
[formGroup]="form" |
||||
|
(ngSubmit)="onSubmit()" |
||||
|
validateOnSubmit |
||||
|
> |
||||
|
<p>{{ 'AbpAccount::SendPasswordResetLink_Information' | abpLocalization }}</p> |
||||
|
<div class="form-group"> |
||||
|
<label for="input-email-address">{{ 'AbpAccount::EmailAddress' | abpLocalization }}</label |
||||
|
><span> * </span> |
||||
|
<input type="email" id="input-email-address" class="form-control" formControlName="email" /> |
||||
|
</div> |
||||
|
<abp-button |
||||
|
class="d-block" |
||||
|
buttonClass="mt-2 mb-3 btn btn-primary btn-block" |
||||
|
[loading]="inProgress" |
||||
|
buttonType="submit" |
||||
|
[disabled]="form?.invalid" |
||||
|
> |
||||
|
{{ 'AbpAccount::Submit' | abpLocalization }} |
||||
|
</abp-button> |
||||
|
<a routerLink="/account/login" |
||||
|
><i class="fa fa-long-arrow-left mr-1"></i>{{ 'AbpAccount::Login' | abpLocalization }}</a |
||||
|
> |
||||
|
</form> |
||||
|
|
||||
|
<ng-template #emailSentTemplate> |
||||
|
<p> |
||||
|
{{ 'AbpAccount::PasswordResetMailSentMessage' | abpLocalization }} |
||||
|
</p> |
||||
|
|
||||
|
<a routerLink="/account/login"> |
||||
|
<button class="d-block mt-2 mb-3 btn btn-primary btn-block"> |
||||
|
<i class="fa fa-long-arrow-left mr-1"></i> |
||||
|
{{ 'AbpAccount::BackToLogin' | abpLocalization }} |
||||
|
</button> |
||||
|
</a> |
||||
|
</ng-template> |
||||
@ -0,0 +1,35 @@ |
|||||
|
import { Component, OnInit } from '@angular/core'; |
||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
||||
|
import { finalize } from 'rxjs/operators'; |
||||
|
import { AccountService } from '../../proxy/account/account.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'abp-forgot-password', |
||||
|
templateUrl: 'forgot-password.component.html', |
||||
|
}) |
||||
|
export class ForgotPasswordComponent { |
||||
|
form: FormGroup; |
||||
|
|
||||
|
inProgress: boolean; |
||||
|
|
||||
|
isEmailSent = false; |
||||
|
|
||||
|
constructor(private fb: FormBuilder, private accountService: AccountService) { |
||||
|
this.form = this.fb.group({ |
||||
|
email: ['', [Validators.required, Validators.email]], |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
onSubmit() { |
||||
|
if (this.form.invalid) return; |
||||
|
|
||||
|
this.inProgress = true; |
||||
|
|
||||
|
this.accountService |
||||
|
.sendPasswordResetCode({ email: this.form.get('email').value, appName: 'Angular' }) |
||||
|
.pipe(finalize(() => (this.inProgress = false))) |
||||
|
.subscribe(() => { |
||||
|
this.isEmailSent = true; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
export * from './change-password/change-password.component'; |
||||
|
export * from './forgot-password/forgot-password.component'; |
||||
|
export * from './login/login.component'; |
||||
|
export * from './manage-profile/manage-profile.component'; |
||||
|
export * from './register/register.component'; |
||||
|
export * from './personal-settings/personal-settings.component'; |
||||
|
export * from './reset-password/reset-password.component'; |
||||
|
export * from './tenant-box/tenant-box.component'; |
||||
@ -0,0 +1,60 @@ |
|||||
|
<h4>{{ 'AbpAccount::Login' | abpLocalization }}</h4> |
||||
|
<strong *ngIf="isSelfRegistrationEnabled"> |
||||
|
{{ 'AbpAccount::AreYouANewUser' | abpLocalization }} |
||||
|
<a class="text-decoration-none" routerLink="/account/register" queryParamsHandling="preserve">{{ |
||||
|
'AbpAccount::Register' | abpLocalization |
||||
|
}}</a> |
||||
|
</strong> |
||||
|
<form [formGroup]="form" (ngSubmit)="onSubmit()" validateOnSubmit class="mt-4"> |
||||
|
<div class="form-group"> |
||||
|
<label for="login-input-user-name-or-email-address">{{ |
||||
|
'AbpAccount::UserNameOrEmailAddress' | abpLocalization |
||||
|
}}</label> |
||||
|
<input |
||||
|
class="form-control" |
||||
|
type="text" |
||||
|
id="login-input-user-name-or-email-address" |
||||
|
formControlName="username" |
||||
|
autocomplete="username" |
||||
|
autofocus |
||||
|
/> |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<label for="login-input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label> |
||||
|
<input |
||||
|
class="form-control" |
||||
|
type="password" |
||||
|
id="login-input-password" |
||||
|
formControlName="password" |
||||
|
autocomplete="current-password" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div class="row"> |
||||
|
<div class="col"> |
||||
|
<label class="custom-checkbox custom-control mb-2" for="login-input-remember-me"> |
||||
|
<input |
||||
|
class="form-check-input" |
||||
|
type="checkbox" |
||||
|
id="login-input-remember-me" |
||||
|
formControlName="rememberMe" |
||||
|
/> |
||||
|
{{ 'AbpAccount::RememberMe' | abpLocalization }} |
||||
|
</label> |
||||
|
</div> |
||||
|
<div class="text-right col"> |
||||
|
<a routerLink="/account/forgot-password">{{ |
||||
|
'AbpAccount::ForgotPassword' | abpLocalization |
||||
|
}}</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<abp-button |
||||
|
[loading]="inProgress" |
||||
|
buttonType="submit" |
||||
|
name="Action" |
||||
|
buttonClass="btn-block btn-lg mt-3 btn btn-primary" |
||||
|
> |
||||
|
{{ 'AbpAccount::Login' | abpLocalization }} |
||||
|
</abp-button> |
||||
|
</form> |
||||
@ -0,0 +1,80 @@ |
|||||
|
import { ConfigStateService, AuthService } from '@abp/ng.core'; |
||||
|
import { ToasterService } from '@abp/ng.theme.shared'; |
||||
|
import { Component, Injector, OnInit } from '@angular/core'; |
||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
||||
|
import { Store } from '@ngxs/store'; |
||||
|
import { throwError } from 'rxjs'; |
||||
|
import { catchError, finalize } from 'rxjs/operators'; |
||||
|
import snq from 'snq'; |
||||
|
import { eAccountComponents } from '../../enums/components'; |
||||
|
import { getRedirectUrl } from '../../utils/auth-utils'; |
||||
|
|
||||
|
const { maxLength, required } = Validators; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'abp-login', |
||||
|
templateUrl: './login.component.html', |
||||
|
}) |
||||
|
export class LoginComponent implements OnInit { |
||||
|
form: FormGroup; |
||||
|
|
||||
|
inProgress: boolean; |
||||
|
|
||||
|
isSelfRegistrationEnabled = true; |
||||
|
|
||||
|
authWrapperKey = eAccountComponents.AuthWrapper; |
||||
|
|
||||
|
constructor( |
||||
|
protected injector: Injector, |
||||
|
protected fb: FormBuilder, |
||||
|
protected toasterService: ToasterService, |
||||
|
protected authService: AuthService, |
||||
|
protected configState: ConfigStateService, |
||||
|
) {} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.init(); |
||||
|
this.buildForm(); |
||||
|
} |
||||
|
|
||||
|
protected init() { |
||||
|
this.isSelfRegistrationEnabled = |
||||
|
( |
||||
|
(this.configState.getSetting('Abp.Account.IsSelfRegistrationEnabled') as string) || '' |
||||
|
).toLowerCase() !== 'false'; |
||||
|
} |
||||
|
|
||||
|
protected buildForm() { |
||||
|
this.form = this.fb.group({ |
||||
|
username: ['', [required, maxLength(255)]], |
||||
|
password: ['', [required, maxLength(128)]], |
||||
|
rememberMe: [false], |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
onSubmit() { |
||||
|
if (this.form.invalid) return; |
||||
|
|
||||
|
this.inProgress = true; |
||||
|
|
||||
|
const { username, password, rememberMe } = this.form.value; |
||||
|
|
||||
|
const redirectUrl = getRedirectUrl(this.injector); |
||||
|
|
||||
|
this.authService |
||||
|
.login({ username, password, rememberMe, redirectUrl }) |
||||
|
.pipe( |
||||
|
catchError(err => { |
||||
|
this.toasterService.error( |
||||
|
snq(() => err.error.error_description) || |
||||
|
snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'), |
||||
|
'Error', |
||||
|
{ life: 7000 }, |
||||
|
); |
||||
|
return throwError(err); |
||||
|
}), |
||||
|
finalize(() => (this.inProgress = false)), |
||||
|
) |
||||
|
.subscribe(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
<div id="AbpContentToolbar"></div> |
||||
|
|
||||
|
<div class="card border-0 shadow-sm min-h-400" [abpLoading]="!(profile$ | async)"> |
||||
|
<div class="card-body"> |
||||
|
<div class="row"> |
||||
|
<div class="col-12 col-md-3"> |
||||
|
<ul class="nav flex-column nav-pills" id="nav-tab" role="tablist"> |
||||
|
<li |
||||
|
*ngIf="!hideChangePasswordTab && (profile$ | async)" |
||||
|
class="nav-item" |
||||
|
(click)="selectedTab = 0" |
||||
|
> |
||||
|
<a |
||||
|
class="nav-link" |
||||
|
[ngClass]="{ active: selectedTab === 0 }" |
||||
|
role="tab" |
||||
|
href="javascript:void(0)" |
||||
|
>{{ 'AbpUi::ChangePassword' | abpLocalization }}</a |
||||
|
> |
||||
|
</li> |
||||
|
<li class="nav-item mb-2" (click)="selectedTab = 1"> |
||||
|
<a |
||||
|
class="nav-link" |
||||
|
[ngClass]="{ active: selectedTab === 1 }" |
||||
|
role="tab" |
||||
|
href="javascript:void(0)" |
||||
|
>{{ 'AbpAccount::PersonalSettings' | abpLocalization }}</a |
||||
|
> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<div *ngIf="profile$ | async" class="col-12 col-md-9"> |
||||
|
<div class="tab-content" *ngIf="selectedTab === 0" [@fadeIn]> |
||||
|
<div class="tab-pane active" role="tabpanel"> |
||||
|
<h4> |
||||
|
{{ 'AbpIdentity::ChangePassword' | abpLocalization }} |
||||
|
<hr /> |
||||
|
</h4> |
||||
|
<abp-change-password-form |
||||
|
*abpReplaceableTemplate="{ |
||||
|
componentKey: changePasswordKey |
||||
|
}" |
||||
|
></abp-change-password-form> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tab-content" *ngIf="selectedTab === 1" [@fadeIn]> |
||||
|
<div class="tab-pane active" role="tabpanel"> |
||||
|
<h4> |
||||
|
{{ 'AbpIdentity::PersonalSettings' | abpLocalization }} |
||||
|
<hr /> |
||||
|
</h4> |
||||
|
<abp-personal-settings-form |
||||
|
*abpReplaceableTemplate="{ |
||||
|
componentKey: personalSettingsKey |
||||
|
}" |
||||
|
></abp-personal-settings-form> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue