From 88309105f368ff7fdf69676ae1b6c0b37a0f9c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=86=9B?= <510423039@qq.com> Date: Sat, 15 May 2021 22:54:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=90=AF=E7=94=A8?= =?UTF-8?q?=E7=A6=81=E7=94=A8=E7=94=A8=E6=88=B7=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=AE=A1=E8=AE=A1=E6=97=A5=E5=BF=97=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...CompanyNameProjectNameHttpApiHostModule.cs | 3 +- .../Audits/Dtos/QueryAuditLogInput.cs | 15 + .../Audits/Dtos/QueryAuditLogOutput.cs | 123 +++ .../Audits/Dtos/QueryEntityChangeInput.cs | 11 + .../Audits/Dtos/QueryEntityChangeOutput.cs | 107 +++ ...ProjectNamePermissionDefinitionProvider.cs | 6 +- .../Users/Dtos/LockUserInput.cs | 16 + .../Audits/AuditAppService.cs | 69 ++ ...ProjectNameApplicationAutoMapperProfile.cs | 12 +- .../Users/LoginAppService.cs | 1 + .../Users/UserAppService.cs | 30 +- .../CompanyNameProjectName/en.json | 6 +- .../CompanyNameProjectName/zh-Hans.json | 5 +- content/vue/package.json | 4 +- .../vue/src/locales/lang/en/routes/admin.ts | 16 +- .../src/locales/lang/zh_CN/routes/admin.ts | 15 +- content/vue/src/main.ts | 4 +- content/vue/src/router/menus/modules/admin.ts | 10 +- .../vue/src/router/routes/modules/admin.ts | 11 +- content/vue/src/services/ServiceProxies.ts | 849 +++++++++++++++++- .../vue/src/views/admin/audits/AuditLog.vue | 91 ++ content/vue/src/views/admin/audits/audit.ts | 296 ++++++ content/vue/src/views/admin/roles/AbpRole.ts | 1 - content/vue/src/views/admin/users/AbpUser.ts | 18 +- content/vue/src/views/admin/users/AbpUser.vue | 35 +- content/vue/src/views/sys/login/Login.vue | 5 +- content/vue/yarn.lock | 36 + 27 files changed, 1726 insertions(+), 69 deletions(-) create mode 100644 content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryAuditLogInput.cs create mode 100644 content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryAuditLogOutput.cs create mode 100644 content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryEntityChangeInput.cs create mode 100644 content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryEntityChangeOutput.cs create mode 100644 content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Users/Dtos/LockUserInput.cs create mode 100644 content/aspnetcore/src/CompanyName.ProjectName.Application/Audits/AuditAppService.cs create mode 100644 content/vue/src/views/admin/audits/AuditLog.vue create mode 100644 content/vue/src/views/admin/audits/audit.ts diff --git a/content/aspnetcore/host/CompanyName.ProjectName.HttpApi.Host/CompanyNameProjectNameHttpApiHostModule.cs b/content/aspnetcore/host/CompanyName.ProjectName.HttpApi.Host/CompanyNameProjectNameHttpApiHostModule.cs index 01800b18..e41e705b 100644 --- a/content/aspnetcore/host/CompanyName.ProjectName.HttpApi.Host/CompanyNameProjectNameHttpApiHostModule.cs +++ b/content/aspnetcore/host/CompanyName.ProjectName.HttpApi.Host/CompanyNameProjectNameHttpApiHostModule.cs @@ -159,7 +159,8 @@ namespace CompanyNameProjectName { Configure(options => { - options.IsEnabled = false; //Disables the auditing system + options.IsEnabled = true; + options.EntityHistorySelectors.AddAllEntities(); }); } diff --git a/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryAuditLogInput.cs b/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryAuditLogInput.cs new file mode 100644 index 00000000..a377e515 --- /dev/null +++ b/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryAuditLogInput.cs @@ -0,0 +1,15 @@ +using CompanyNameProjectName.Pages.Dtos; + +namespace CompanyNameProjectName.Audits.Dtos +{ + public class QueryAuditLogInput : CustomeRequestDto + { + public string UserName { get; set; } + + public int HttpStatusCode { get; set; } + + public string HttpMethod { get; set; } + + public string ExecutionTime { get; set; } + } +} diff --git a/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryAuditLogOutput.cs b/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryAuditLogOutput.cs new file mode 100644 index 00000000..cd564e79 --- /dev/null +++ b/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryAuditLogOutput.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Application.Dtos; + +namespace CompanyNameProjectName.Audits.Dtos +{ + public class QueryAuditLogOutput : EntityDto + { + public string ApplicationName + { + get; + set; + } + + public Guid? UserId + { + get; + set; + } + + public string UserName + { + get; + set; + } + + public Guid? TenantId + { + get; + set; + } + + public string TenantName + { + get; + set; + } + + public Guid? ImpersonatorUserId + { + get; + set; + } + + public Guid? ImpersonatorTenantId + { + get; + set; + } + + public virtual DateTime ExecutionTime + { + get; + set; + } + + public int ExecutionDuration + { + get; + set; + } + + public string ClientIpAddress + { + get; + set; + } + + public string ClientName + { + get; + set; + } + + public string ClientId + { + get; + set; + } + + public string CorrelationId + { + get; + set; + } + + public string BrowserInfo + { + get; + set; + } + + public string HttpMethod + { + get; + set; + } + + public string Url + { + get; + set; + } + + public string Exceptions + { + get; + set; + } + + public string Comments + { + get; + set; + } + + public int? HttpStatusCode + { + get; + set; + } + } +} diff --git a/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryEntityChangeInput.cs b/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryEntityChangeInput.cs new file mode 100644 index 00000000..d3c10733 --- /dev/null +++ b/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryEntityChangeInput.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CompanyNameProjectName.Audits.Dtos +{ + public class QueryEntityChangeInput + { + public Guid Id { get; set; } + } +} diff --git a/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryEntityChangeOutput.cs b/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryEntityChangeOutput.cs new file mode 100644 index 00000000..6f138215 --- /dev/null +++ b/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Audits/Dtos/QueryEntityChangeOutput.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Auditing; +using Volo.Abp.Data; + +namespace CompanyNameProjectName.Audits.Dtos +{ + public class QueryEntityChangeOutput : EntityDto + { + public Guid AuditLogId + { + get; + set; + } + + public Guid? TenantId + { + get; + set; + } + + public DateTime ChangeTime + { + get; + set; + } + + public EntityChangeType ChangeType + { + get; + set; + } + + public Guid? EntityTenantId + { + get; + set; + } + + public string EntityId + { + get; + set; + } + + public string EntityTypeFullName + { + get; + set; + } + + public ICollection PropertyChanges + { + get; + set; + } + + public ExtraPropertyDictionary ExtraProperties + { + get; + set; + } + + } + + public class PropertyChangesDto : EntityDto + { + public Guid? TenantId + { + get; + set; + } + + public Guid EntityChangeId + { + get; + set; + } + + public string NewValue + { + get; + set; + } + + public string OriginalValue + { + get; + set; + } + + public string PropertyName + { + get; + set; + } + + public string PropertyTypeFullName + { + get; + set; + } + + } +} diff --git a/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Permissions/CompanyNameProjectNamePermissionDefinitionProvider.cs b/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Permissions/CompanyNameProjectNamePermissionDefinitionProvider.cs index d38481e2..4b731de5 100644 --- a/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Permissions/CompanyNameProjectNamePermissionDefinitionProvider.cs +++ b/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Permissions/CompanyNameProjectNamePermissionDefinitionProvider.cs @@ -8,11 +8,11 @@ namespace CompanyNameProjectName.Permissions { public override void Define(IPermissionDefinitionContext context) { - - + var abpIdentityGroup = context.GetGroup("AbpIdentity"); + abpIdentityGroup.AddPermission("AbpIdentity.Users.Lock", L("Permission:Users:Enable")); + abpIdentityGroup.AddPermission("AbpIdentity.Users.AuditLog", L("Permission:Users:AuditLog")); //Define your own permissions here. Example: //myGroup.AddPermission(CompanyNameProjectNamePermissions.MyPermission1, L("Permission:MyPermission1")); - } private static LocalizableString L(string name) diff --git a/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Users/Dtos/LockUserInput.cs b/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Users/Dtos/LockUserInput.cs new file mode 100644 index 00000000..d81ca8f1 --- /dev/null +++ b/content/aspnetcore/src/CompanyName.ProjectName.Application.Contracts/Users/Dtos/LockUserInput.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; + +namespace CompanyNameProjectName.Users.Dtos +{ + public class LockUserInput + { + [Required] + public Guid UserId { get; set; } + + [Required] + public bool Locked { get; set; } + } +} diff --git a/content/aspnetcore/src/CompanyName.ProjectName.Application/Audits/AuditAppService.cs b/content/aspnetcore/src/CompanyName.ProjectName.Application/Audits/AuditAppService.cs new file mode 100644 index 00000000..300bcd43 --- /dev/null +++ b/content/aspnetcore/src/CompanyName.ProjectName.Application/Audits/AuditAppService.cs @@ -0,0 +1,69 @@ +using CompanyNameProjectName.Audits.Dtos; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; +using Swashbuckle.AspNetCore.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.AuditLogging; +using Volo.Abp.Domain.Repositories; + + +namespace CompanyNameProjectName.Audits +{ + [Authorize("AbpIdentity.Users.AuditLog")] + public class AuditAppService : ApplicationService + { + private readonly IRepository _auditLogRepository; + + public AuditAppService(IRepository auditLogRepository) + { + _auditLogRepository = auditLogRepository; + } + + /// + /// 分页查询审计日志 + /// + /// + /// + [SwaggerOperation(summary: "分页获取审计日志信息", Tags = new[] { "Audit" })] + public async Task> ListAsync(QueryAuditLogInput input) + { + + var query = _auditLogRepository + .WhereIf(!input.ExecutionTime.IsNullOrWhiteSpace(), e => e.ExecutionTime > Convert.ToDateTime(input.ExecutionTime).AddSeconds(-1)) + .WhereIf(!input.ExecutionTime.IsNullOrWhiteSpace(), e => e.ExecutionTime < Convert.ToDateTime(input.ExecutionTime).AddDays(1).AddSeconds(-1)) + .WhereIf(!input.UserName.IsNullOrWhiteSpace(), e => e.UserName == input.UserName.Trim()) + .WhereIf(!input.HttpMethod.IsNullOrWhiteSpace(), e => e.HttpMethod == input.HttpMethod) + .WhereIf(input.HttpStatusCode > 0, e => e.HttpStatusCode == input.HttpStatusCode); + + var totalCount = await query.CountAsync(); + var maxResultCount = input.PageSize; + var skipCount = (input.PageIndex - 1) * input.PageSize; + var list = await query.OrderByDescending(e => e.ExecutionTime).PageBy(skipCount, maxResultCount).ToListAsync(); + return new PagedResultDto( + totalCount, + ObjectMapper.Map, List>(list) + ); + } + + /// + /// 查询审计日志的实体变化信息 + /// + /// + /// + [SwaggerOperation(summary: "获取审计日志详情", Tags = new[] { "Audit" })] + public async Task> QueryEntity(QueryEntityChangeInput input) + { + var entity = await _auditLogRepository.IncludeDetails().FirstOrDefaultAsync(e => e.Id == input.Id); + if (entity != null) + { + return ObjectMapper.Map, List>(entity.EntityChanges.ToList()); + } + return null; + } + } +} diff --git a/content/aspnetcore/src/CompanyName.ProjectName.Application/CompanyNameProjectNameApplicationAutoMapperProfile.cs b/content/aspnetcore/src/CompanyName.ProjectName.Application/CompanyNameProjectNameApplicationAutoMapperProfile.cs index 0b3c68f1..eea4c875 100644 --- a/content/aspnetcore/src/CompanyName.ProjectName.Application/CompanyNameProjectNameApplicationAutoMapperProfile.cs +++ b/content/aspnetcore/src/CompanyName.ProjectName.Application/CompanyNameProjectNameApplicationAutoMapperProfile.cs @@ -1,5 +1,7 @@ using AutoMapper; +using CompanyNameProjectName.Audits.Dtos; using CompanyNameProjectName.Dtos.Users; +using Volo.Abp.AuditLogging; using Volo.Abp.Identity; namespace CompanyNameProjectName @@ -11,8 +13,16 @@ namespace CompanyNameProjectName /* You can configure your AutoMapper mapping configuration here. * Alternatively, you can split your mapping configurations * into multiple profile classes for a better organization. */ - + #region 用户 CreateMap(); + #endregion + + + #region 审计日志 + CreateMap(); + CreateMap(); + CreateMap(); + #endregion } } } diff --git a/content/aspnetcore/src/CompanyName.ProjectName.Application/Users/LoginAppService.cs b/content/aspnetcore/src/CompanyName.ProjectName.Application/Users/LoginAppService.cs index df44c7a2..fa3b36da 100644 --- a/content/aspnetcore/src/CompanyName.ProjectName.Application/Users/LoginAppService.cs +++ b/content/aspnetcore/src/CompanyName.ProjectName.Application/Users/LoginAppService.cs @@ -41,6 +41,7 @@ namespace CompanyNameProjectName.Users try { var result = await _signInManager.PasswordSignInAsync(input.Name, input.Password, false, true); + if (result.IsLockedOut) throw new Exception("当前用户已被锁定"); if (!result.Succeeded) throw new Exception("用户名或者密码错误"); var user = await _userManager.FindByNameAsync(input.Name); var roles = await _userManager.GetRolesAsync(user); diff --git a/content/aspnetcore/src/CompanyName.ProjectName.Application/Users/UserAppService.cs b/content/aspnetcore/src/CompanyName.ProjectName.Application/Users/UserAppService.cs index caeb1e1a..2b004e12 100644 --- a/content/aspnetcore/src/CompanyName.ProjectName.Application/Users/UserAppService.cs +++ b/content/aspnetcore/src/CompanyName.ProjectName.Application/Users/UserAppService.cs @@ -35,14 +35,14 @@ namespace CompanyNameProjectName.Users request.Filter = input.filter?.Trim(); request.MaxResultCount = input.PageSize; request.SkipCount = (input.PageIndex - 1) * input.PageSize; - request.Sorting= " LastModificationTime desc"; + request.Sorting = " LastModificationTime desc"; return await _identityUserAppService.GetListAsync(request); } [SwaggerOperation(summary: "创建用户", Tags = new[] { "User" })] [Authorize("AbpIdentity.Users.Create")] [HttpPost] - public async Task CreateAsync(IdentityUserCreateDto input) + public async Task CreateAsync(IdentityUserCreateDto input) { return await _identityUserAppService.CreateAsync(input); } @@ -52,20 +52,20 @@ namespace CompanyNameProjectName.Users [HttpPost] public virtual async Task UpdateAsync(UpdateUserInput input) { - return await _identityUserAppService.UpdateAsync(input.UserId,input.UserInfo); + return await _identityUserAppService.UpdateAsync(input.UserId, input.UserInfo); } [SwaggerOperation(summary: "删除用户", Tags = new[] { "User" })] [Authorize("AbpIdentity.Users.Delete")] public virtual async Task DeleteAsync(Guid id) { - await _identityUserAppService.DeleteAsync(id); + await _identityUserAppService.DeleteAsync(id); } [SwaggerOperation(summary: "获取用户角色", Tags = new[] { "User" })] [Authorize("AbpIdentity.Users")] [HttpPost("/api/app/user/role/{userId}")] - public async Task> GetRoleByUserId(Guid userId) + public async Task> GetRoleByUserId(Guid userId) { return await _identityUserAppService.GetRolesAsync(userId); } @@ -91,5 +91,25 @@ namespace CompanyNameProjectName.Users if (!result.Succeeded) throw new Exception(result.Errors.FirstOrDefault().Description); return result.Succeeded; } + + /// + /// 锁定用户 + /// + /// + /// + public async Task LockAsync(LockUserInput input) + { + var identityUser = await _userManager.GetByIdAsync(input.UserId); + await _userManager.SetLockoutEnabledAsync(identityUser, input.Locked); + if (input.Locked) + { + // 如果锁定用户,锁定100年 + await _userManager.SetLockoutEndDateAsync(identityUser, DateTimeOffset.UtcNow.AddYears(100)); + } + else { + await _userManager.SetLockoutEndDateAsync(identityUser, DateTimeOffset.UtcNow.AddDays(-1)); + } + + } } } diff --git a/content/aspnetcore/src/CompanyName.ProjectName.Domain.Shared/Localization/CompanyNameProjectName/en.json b/content/aspnetcore/src/CompanyName.ProjectName.Domain.Shared/Localization/CompanyNameProjectName/en.json index f4104af3..237f7ceb 100644 --- a/content/aspnetcore/src/CompanyName.ProjectName.Domain.Shared/Localization/CompanyNameProjectName/en.json +++ b/content/aspnetcore/src/CompanyName.ProjectName.Domain.Shared/Localization/CompanyNameProjectName/en.json @@ -6,6 +6,10 @@ "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.", "DataExistence": "Existence", "DataNotExistence": "Not Existence", - "TestSettings": "TestSettings" + "TestSettings": "TestSettings", + + "Permission:Users:Enable": "Enable|Disable", + "Permission:Users:AuditLog": "AuditLog" + } } diff --git a/content/aspnetcore/src/CompanyName.ProjectName.Domain.Shared/Localization/CompanyNameProjectName/zh-Hans.json b/content/aspnetcore/src/CompanyName.ProjectName.Domain.Shared/Localization/CompanyNameProjectName/zh-Hans.json index 38b71c33..a0d5eb5b 100644 --- a/content/aspnetcore/src/CompanyName.ProjectName.Domain.Shared/Localization/CompanyNameProjectName/zh-Hans.json +++ b/content/aspnetcore/src/CompanyName.ProjectName.Domain.Shared/Localization/CompanyNameProjectName/zh-Hans.json @@ -7,6 +7,9 @@ "DataExistence": "已存在", "DataNotExistence": "不存在", "Permission:Query": "查询", - "TestSettings": "测试Settings" + "TestSettings": "测试Settings", + + "Permission:Users:Enable": "启用|禁用", + "Permission:Users:AuditLog": "审计日志" } } \ No newline at end of file diff --git a/content/vue/package.json b/content/vue/package.json index 6a14bda6..185b2798 100644 --- a/content/vue/package.json +++ b/content/vue/package.json @@ -38,6 +38,7 @@ "@zxcvbn-ts/core": "^0.3.0", "ant-design-vue": "^2.1.2", "axios": "^0.21.1", + "clipboard": "^2.0.8", "crypto-js": "^4.0.0", "echarts": "^5.0.2", "jwt-decode": "^3.1.2", @@ -52,7 +53,8 @@ "vue": "3.0.11", "vue-i18n": "9.0.0", "vue-router": "^4.0.6", - "vue-types": "^3.0.2" + "vue-types": "^3.0.2", + "vue3-json-viewer": "^1.0.4" }, "devDependencies": { "@commitlint/cli": "^12.1.1", diff --git a/content/vue/src/locales/lang/en/routes/admin.ts b/content/vue/src/locales/lang/en/routes/admin.ts index 868f7753..21241b0b 100644 --- a/content/vue/src/locales/lang/en/routes/admin.ts +++ b/content/vue/src/locales/lang/en/routes/admin.ts @@ -6,6 +6,7 @@ export default { userManagement_userName: 'UserName', userManagement_name: "Name", userManagement_email: "Email", + userManagement_locked: "Locked", userManagement_phone: "phone", userManagement_createTime: "CreateTime", userManagement_create_user: 'Create User', @@ -18,5 +19,18 @@ export default { roleManagement_default: "Default", roleManagement_edit: "Edit Role", roleManagement_create_role: "Create Role", - roleManagement_permission: "Permission" + roleManagement_permission: "Permission", + + auditManagement: "Audit Log", + audit_executeTime: "ExecutionTime", + audit_endTime: "EndTime", + audit_userName: "UserName", + audit_httpMethod: "HttpMethod", + audit_httpStatusCode: "HttpStatusCode", + audit_httpRequest: "HttpRequest", + audit_ipAdrress: "IP Address", + audit_duration: "Execution Duration(ms)", + audit_url: "URL", + audit_entityInfo: "EntityInformation", + audit_message: "Entity Message", }; diff --git a/content/vue/src/locales/lang/zh_CN/routes/admin.ts b/content/vue/src/locales/lang/zh_CN/routes/admin.ts index e1a4f775..10bbb479 100644 --- a/content/vue/src/locales/lang/zh_CN/routes/admin.ts +++ b/content/vue/src/locales/lang/zh_CN/routes/admin.ts @@ -6,6 +6,7 @@ export default { userManagement_userName: '用户名称', userManagement_name: "名称", userManagement_email: "邮箱", + userManagement_locked: "锁定", userManagement_phone: "手机", userManagement_createTime: "创建时间", userManagement_password: "密码", @@ -17,5 +18,17 @@ export default { roleManagement_default: "默认", roleManagement_edit: "编辑角色", roleManagement_create_role: "创建角色", - roleManagement_permission: "授权" + roleManagement_permission: "授权", + + auditManagement: "审计日志", + audit_userName: "用户名", + audit_httpMethod: "Http方法", + audit_httpStatusCode: "Http状态码", + audit_httpRequest: "Http请求", + audit_ipAdrress: "IP地址", + audit_executeTime: "执行时间", + audit_duration: "持续时间(ms)", + audit_url: "Url地址", + audit_entityInfo: "实体信息", + audit_message: "实体信息", }; diff --git a/content/vue/src/main.ts b/content/vue/src/main.ts index 35358abd..723cc180 100644 --- a/content/vue/src/main.ts +++ b/content/vue/src/main.ts @@ -11,7 +11,7 @@ import { setupErrorHandle } from '/@/logics/error-handle'; import { setupGlobDirectives } from '/@/directives'; import { setupI18n } from '/@/locales/setupI18n'; import { registerGlobComp } from '/@/components/registerGlobComp'; - +import JsonViewer from "vue3-json-viewer" // Register icon Sprite import 'vite-plugin-svg-icons/register'; @@ -25,7 +25,7 @@ if (import.meta.env.DEV) { (async () => { const app = createApp(App); - + app.use(JsonViewer); // Configure vuex store setupStore(app); diff --git a/content/vue/src/router/menus/modules/admin.ts b/content/vue/src/router/menus/modules/admin.ts index 125d5e21..b20e036e 100644 --- a/content/vue/src/router/menus/modules/admin.ts +++ b/content/vue/src/router/menus/modules/admin.ts @@ -12,12 +12,20 @@ const admin: MenuModule = { tag: { type: 'warn', }, - }, { + }, + { path: 'abpRole', name: t('routes.admin.roleManagement'), tag: { type: 'warn', }, + }, + { + path: 'audit', + name: t('routes.admin.auditManagement'), + tag: { + type: 'warn', + }, }] }, }; diff --git a/content/vue/src/router/routes/modules/admin.ts b/content/vue/src/router/routes/modules/admin.ts index 8a66bc8c..9243bd4e 100644 --- a/content/vue/src/router/routes/modules/admin.ts +++ b/content/vue/src/router/routes/modules/admin.ts @@ -6,7 +6,6 @@ const admin: AppRouteModule = { path: '/admin', name: 'Admin', component: LAYOUT, - //redirect: '/admin/abpUser', meta: { icon: 'ion:grid-outline', title: t('routes.admin.systemManagement'), @@ -31,6 +30,16 @@ const admin: AppRouteModule = { policy: 'AbpIdentity.Roles', icon: 'ant-design:lock-outlined' }, + }, + { + path: 'audit', + name: 'Audit', + component: () => import('/@/views/admin/audits/AuditLog.vue'), + meta: { + title: t('routes.admin.auditManagement'), + policy: "AbpIdentity.Users.AuditLog", + icon: 'ant-design:audit-outlined' + }, } ], }; diff --git a/content/vue/src/services/ServiceProxies.ts b/content/vue/src/services/ServiceProxies.ts index 9bcbbf1d..1f0e4b3a 100644 --- a/content/vue/src/services/ServiceProxies.ts +++ b/content/vue/src/services/ServiceProxies.ts @@ -215,6 +215,210 @@ export class AbpApplicationConfigurationServiceProxy extends ServiceProxyBase { } } +export class AuditServiceProxy extends ServiceProxyBase { + private instance: AxiosInstance; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(baseUrl?: string, instance?: AxiosInstance) { + super(); + this.instance = instance ? instance : axios.create(); + this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : ""; + } + + /** + * 分页获取审计日志信息 + * @param body (optional) + * @return Success + */ + list(body: QueryAuditLogInput | undefined , cancelToken?: CancelToken | undefined): Promise { + let url_ = this.baseUrl + "/api/app/audit/list"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(body); + + let options_ = { + data: content_, + method: "POST", + url: url_, + headers: { + "Content-Type": "application/json", + "Accept": "text/plain" + }, + cancelToken + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.instance.request(transformedOptions_); + }).catch((_error: any) => { + if (isAxiosError(_error) && _error.response) { + return _error.response; + } else { + throw _error; + } + }).then((_response: AxiosResponse) => { + return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processList(_response)); + }); + } + + protected processList(response: AxiosResponse): Promise { + const status = response.status; + let _headers: any = {}; + if (response.headers && typeof response.headers === "object") { + for (let k in response.headers) { + if (response.headers.hasOwnProperty(k)) { + _headers[k] = response.headers[k]; + } + } + } + if (status === 200) { + const _responseText = response.data; + let result200: any = null; + let resultData200 = _responseText; + result200 = QueryAuditLogOutputPagedResultDto.fromJS(resultData200); + return result200; + } else if (status === 403) { + const _responseText = response.data; + let result403: any = null; + let resultData403 = _responseText; + result403 = RemoteServiceErrorResponse.fromJS(resultData403); + return throwException("Forbidden", status, _responseText, _headers, result403); + } else if (status === 401) { + const _responseText = response.data; + let result401: any = null; + let resultData401 = _responseText; + result401 = RemoteServiceErrorResponse.fromJS(resultData401); + return throwException("Unauthorized", status, _responseText, _headers, result401); + } else if (status === 400) { + const _responseText = response.data; + let result400: any = null; + let resultData400 = _responseText; + result400 = RemoteServiceErrorResponse.fromJS(resultData400); + return throwException("Bad Request", status, _responseText, _headers, result400); + } else if (status === 404) { + const _responseText = response.data; + let result404: any = null; + let resultData404 = _responseText; + result404 = RemoteServiceErrorResponse.fromJS(resultData404); + return throwException("Not Found", status, _responseText, _headers, result404); + } else if (status === 501) { + const _responseText = response.data; + let result501: any = null; + let resultData501 = _responseText; + result501 = RemoteServiceErrorResponse.fromJS(resultData501); + return throwException("Server Error", status, _responseText, _headers, result501); + } else if (status === 500) { + const _responseText = response.data; + let result500: any = null; + let resultData500 = _responseText; + result500 = RemoteServiceErrorResponse.fromJS(resultData500); + return throwException("Server Error", status, _responseText, _headers, result500); + } else if (status !== 200 && status !== 204) { + const _responseText = response.data; + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + } + return Promise.resolve(null); + } + + /** + * 获取审计日志详情 + * @param body (optional) + * @return Success + */ + queryEntity(body: QueryEntityChangeInput | undefined , cancelToken?: CancelToken | undefined): Promise { + let url_ = this.baseUrl + "/api/app/audit/query-entity"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(body); + + let options_ = { + data: content_, + method: "POST", + url: url_, + headers: { + "Content-Type": "application/json", + "Accept": "text/plain" + }, + cancelToken + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.instance.request(transformedOptions_); + }).catch((_error: any) => { + if (isAxiosError(_error) && _error.response) { + return _error.response; + } else { + throw _error; + } + }).then((_response: AxiosResponse) => { + return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processQueryEntity(_response)); + }); + } + + protected processQueryEntity(response: AxiosResponse): Promise { + const status = response.status; + let _headers: any = {}; + if (response.headers && typeof response.headers === "object") { + for (let k in response.headers) { + if (response.headers.hasOwnProperty(k)) { + _headers[k] = response.headers[k]; + } + } + } + if (status === 200) { + const _responseText = response.data; + let result200: any = null; + let resultData200 = _responseText; + if (Array.isArray(resultData200)) { + result200 = [] as any; + for (let item of resultData200) + result200!.push(QueryEntityChangeOutput.fromJS(item)); + } + return result200; + } else if (status === 403) { + const _responseText = response.data; + let result403: any = null; + let resultData403 = _responseText; + result403 = RemoteServiceErrorResponse.fromJS(resultData403); + return throwException("Forbidden", status, _responseText, _headers, result403); + } else if (status === 401) { + const _responseText = response.data; + let result401: any = null; + let resultData401 = _responseText; + result401 = RemoteServiceErrorResponse.fromJS(resultData401); + return throwException("Unauthorized", status, _responseText, _headers, result401); + } else if (status === 400) { + const _responseText = response.data; + let result400: any = null; + let resultData400 = _responseText; + result400 = RemoteServiceErrorResponse.fromJS(resultData400); + return throwException("Bad Request", status, _responseText, _headers, result400); + } else if (status === 404) { + const _responseText = response.data; + let result404: any = null; + let resultData404 = _responseText; + result404 = RemoteServiceErrorResponse.fromJS(resultData404); + return throwException("Not Found", status, _responseText, _headers, result404); + } else if (status === 501) { + const _responseText = response.data; + let result501: any = null; + let resultData501 = _responseText; + result501 = RemoteServiceErrorResponse.fromJS(resultData501); + return throwException("Server Error", status, _responseText, _headers, result501); + } else if (status === 500) { + const _responseText = response.data; + let result500: any = null; + let resultData500 = _responseText; + result500 = RemoteServiceErrorResponse.fromJS(resultData500); + return throwException("Server Error", status, _responseText, _headers, result500); + } else if (status !== 200 && status !== 204) { + const _responseText = response.data; + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + } + return Promise.resolve(null); + } +} + export class LoginServiceProxy extends ServiceProxyBase { private instance: AxiosInstance; private baseUrl: string; @@ -1828,53 +2032,142 @@ export class UserServiceProxy extends ServiceProxyBase { } return Promise.resolve(null); } -} - -export class AbpLoginResult implements IAbpLoginResult { - result?: LoginResultType; - readonly description?: string | undefined; - constructor(data?: IAbpLoginResult) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (this)[property] = (data)[property]; - } - } - } + /** + * @param body (optional) + * @return Success + */ + lock(body: LockUserInput | undefined , cancelToken?: CancelToken | undefined): Promise { + let url_ = this.baseUrl + "/api/app/user/lock"; + url_ = url_.replace(/[?&]$/, ""); - init(_data?: any) { - if (_data) { - this.result = _data["result"]; - (this).description = _data["description"]; - } - } + const content_ = JSON.stringify(body); - static fromJS(data: any): AbpLoginResult { - data = typeof data === 'object' ? data : {}; - let result = new AbpLoginResult(); - result.init(data); - return result; - } + let options_ = { + data: content_, + method: "POST", + url: url_, + headers: { + "Content-Type": "application/json", + }, + cancelToken + }; - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["result"] = this.result; - data["description"] = this.description; - return data; + return this.transformOptions(options_).then(transformedOptions_ => { + return this.instance.request(transformedOptions_); + }).catch((_error: any) => { + if (isAxiosError(_error) && _error.response) { + return _error.response; + } else { + throw _error; + } + }).then((_response: AxiosResponse) => { + return this.transformResult(url_, _response, (_response: AxiosResponse) => this.processLock(_response)); + }); } -} - -export interface IAbpLoginResult { - result?: LoginResultType; - description?: string | undefined; -} -export class ActionApiDescriptionModel implements IActionApiDescriptionModel { - uniqueName?: string | undefined; - name?: string | undefined; - httpMethod?: string | undefined; - url?: string | undefined; + protected processLock(response: AxiosResponse): Promise { + const status = response.status; + let _headers: any = {}; + if (response.headers && typeof response.headers === "object") { + for (let k in response.headers) { + if (response.headers.hasOwnProperty(k)) { + _headers[k] = response.headers[k]; + } + } + } + if (status === 200) { + const _responseText = response.data; + return Promise.resolve(null); + } else if (status === 403) { + const _responseText = response.data; + let result403: any = null; + let resultData403 = _responseText; + result403 = RemoteServiceErrorResponse.fromJS(resultData403); + return throwException("Forbidden", status, _responseText, _headers, result403); + } else if (status === 401) { + const _responseText = response.data; + let result401: any = null; + let resultData401 = _responseText; + result401 = RemoteServiceErrorResponse.fromJS(resultData401); + return throwException("Unauthorized", status, _responseText, _headers, result401); + } else if (status === 400) { + const _responseText = response.data; + let result400: any = null; + let resultData400 = _responseText; + result400 = RemoteServiceErrorResponse.fromJS(resultData400); + return throwException("Bad Request", status, _responseText, _headers, result400); + } else if (status === 404) { + const _responseText = response.data; + let result404: any = null; + let resultData404 = _responseText; + result404 = RemoteServiceErrorResponse.fromJS(resultData404); + return throwException("Not Found", status, _responseText, _headers, result404); + } else if (status === 501) { + const _responseText = response.data; + let result501: any = null; + let resultData501 = _responseText; + result501 = RemoteServiceErrorResponse.fromJS(resultData501); + return throwException("Server Error", status, _responseText, _headers, result501); + } else if (status === 500) { + const _responseText = response.data; + let result500: any = null; + let resultData500 = _responseText; + result500 = RemoteServiceErrorResponse.fromJS(resultData500); + return throwException("Server Error", status, _responseText, _headers, result500); + } else if (status !== 200 && status !== 204) { + const _responseText = response.data; + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + } + return Promise.resolve(null); + } +} + +export class AbpLoginResult implements IAbpLoginResult { + result?: LoginResultType; + readonly description?: string | undefined; + + constructor(data?: IAbpLoginResult) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.result = _data["result"]; + (this).description = _data["description"]; + } + } + + static fromJS(data: any): AbpLoginResult { + data = typeof data === 'object' ? data : {}; + let result = new AbpLoginResult(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["result"] = this.result; + data["description"] = this.description; + return data; + } +} + +export interface IAbpLoginResult { + result?: LoginResultType; + description?: string | undefined; +} + +export class ActionApiDescriptionModel implements IActionApiDescriptionModel { + uniqueName?: string | undefined; + name?: string | undefined; + httpMethod?: string | undefined; + url?: string | undefined; supportedVersions?: string[] | undefined; parametersOnMethod?: MethodParameterApiDescriptionModel[] | undefined; parameters?: ParameterApiDescriptionModel[] | undefined; @@ -2790,6 +3083,12 @@ export interface IDateTimeFormatDto { longTimePattern?: string | undefined; } +export enum EntityChangeType { + _0 = 0, + _1 = 1, + _2 = 2, +} + export class EntityExtensionDto implements IEntityExtensionDto { properties?: { [key: string]: ExtensionPropertyDto; } | undefined; configuration?: { [key: string]: any; } | undefined; @@ -4701,6 +5000,46 @@ export interface ILocalizableStringDto { resource?: string | undefined; } +export class LockUserInput implements ILockUserInput { + userId!: string; + locked!: boolean; + + constructor(data?: ILockUserInput) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.userId = _data["userId"]; + this.locked = _data["locked"]; + } + } + + static fromJS(data: any): LockUserInput { + data = typeof data === 'object' ? data : {}; + let result = new LockUserInput(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["userId"] = this.userId; + data["locked"] = this.locked; + return data; + } +} + +export interface ILockUserInput { + userId: string; + locked: boolean; +} + export class LoginInputDto implements ILoginInputDto { name?: string | undefined; password?: string | undefined; @@ -5449,6 +5788,66 @@ export interface IPropertyApiDescriptionModel { isRequired?: boolean; } +export class PropertyChangesDto implements IPropertyChangesDto { + id?: string; + tenantId?: string | undefined; + entityChangeId?: string; + newValue?: string | undefined; + originalValue?: string | undefined; + propertyName?: string | undefined; + propertyTypeFullName?: string | undefined; + + constructor(data?: IPropertyChangesDto) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.id = _data["id"]; + this.tenantId = _data["tenantId"]; + this.entityChangeId = _data["entityChangeId"]; + this.newValue = _data["newValue"]; + this.originalValue = _data["originalValue"]; + this.propertyName = _data["propertyName"]; + this.propertyTypeFullName = _data["propertyTypeFullName"]; + } + } + + static fromJS(data: any): PropertyChangesDto { + data = typeof data === 'object' ? data : {}; + let result = new PropertyChangesDto(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["id"] = this.id; + data["tenantId"] = this.tenantId; + data["entityChangeId"] = this.entityChangeId; + data["newValue"] = this.newValue; + data["originalValue"] = this.originalValue; + data["propertyName"] = this.propertyName; + data["propertyTypeFullName"] = this.propertyTypeFullName; + return data; + } +} + +export interface IPropertyChangesDto { + id?: string; + tenantId?: string | undefined; + entityChangeId?: string; + newValue?: string | undefined; + originalValue?: string | undefined; + propertyName?: string | undefined; + propertyTypeFullName?: string | undefined; +} + export class ProviderInfoDto implements IProviderInfoDto { providerName?: string | undefined; providerKey?: string | undefined; @@ -5489,6 +5888,374 @@ export interface IProviderInfoDto { providerKey?: string | undefined; } +export class QueryAuditLogInput implements IQueryAuditLogInput { + pageIndex?: number; + pageSize?: number; + userName?: string | undefined; + httpStatusCode?: number; + httpMethod?: string | undefined; + executionTime?: string | undefined; + + constructor(data?: IQueryAuditLogInput) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.pageIndex = _data["pageIndex"]; + this.pageSize = _data["pageSize"]; + this.userName = _data["userName"]; + this.httpStatusCode = _data["httpStatusCode"]; + this.httpMethod = _data["httpMethod"]; + this.executionTime = _data["executionTime"]; + } + } + + static fromJS(data: any): QueryAuditLogInput { + data = typeof data === 'object' ? data : {}; + let result = new QueryAuditLogInput(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["pageIndex"] = this.pageIndex; + data["pageSize"] = this.pageSize; + data["userName"] = this.userName; + data["httpStatusCode"] = this.httpStatusCode; + data["httpMethod"] = this.httpMethod; + data["executionTime"] = this.executionTime; + return data; + } +} + +export interface IQueryAuditLogInput { + pageIndex?: number; + pageSize?: number; + userName?: string | undefined; + httpStatusCode?: number; + httpMethod?: string | undefined; + executionTime?: string | undefined; +} + +export class QueryAuditLogOutput implements IQueryAuditLogOutput { + id?: string; + applicationName?: string | undefined; + userId?: string | undefined; + userName?: string | undefined; + tenantId?: string | undefined; + tenantName?: string | undefined; + impersonatorUserId?: string | undefined; + impersonatorTenantId?: string | undefined; + executionTime?: Date; + executionDuration?: number; + clientIpAddress?: string | undefined; + clientName?: string | undefined; + clientId?: string | undefined; + correlationId?: string | undefined; + browserInfo?: string | undefined; + httpMethod?: string | undefined; + url?: string | undefined; + exceptions?: string | undefined; + comments?: string | undefined; + httpStatusCode?: number | undefined; + readonly entityChanges?: QueryEntityChangeOutput[] | undefined; + readonly actions?: PropertyChangesDto[] | undefined; + + constructor(data?: IQueryAuditLogOutput) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.id = _data["id"]; + this.applicationName = _data["applicationName"]; + this.userId = _data["userId"]; + this.userName = _data["userName"]; + this.tenantId = _data["tenantId"]; + this.tenantName = _data["tenantName"]; + this.impersonatorUserId = _data["impersonatorUserId"]; + this.impersonatorTenantId = _data["impersonatorTenantId"]; + this.executionTime = _data["executionTime"] ? new Date(_data["executionTime"].toString()) : undefined; + this.executionDuration = _data["executionDuration"]; + this.clientIpAddress = _data["clientIpAddress"]; + this.clientName = _data["clientName"]; + this.clientId = _data["clientId"]; + this.correlationId = _data["correlationId"]; + this.browserInfo = _data["browserInfo"]; + this.httpMethod = _data["httpMethod"]; + this.url = _data["url"]; + this.exceptions = _data["exceptions"]; + this.comments = _data["comments"]; + this.httpStatusCode = _data["httpStatusCode"]; + if (Array.isArray(_data["entityChanges"])) { + (this).entityChanges = [] as any; + for (let item of _data["entityChanges"]) + (this).entityChanges!.push(QueryEntityChangeOutput.fromJS(item)); + } + if (Array.isArray(_data["actions"])) { + (this).actions = [] as any; + for (let item of _data["actions"]) + (this).actions!.push(PropertyChangesDto.fromJS(item)); + } + } + } + + static fromJS(data: any): QueryAuditLogOutput { + data = typeof data === 'object' ? data : {}; + let result = new QueryAuditLogOutput(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["id"] = this.id; + data["applicationName"] = this.applicationName; + data["userId"] = this.userId; + data["userName"] = this.userName; + data["tenantId"] = this.tenantId; + data["tenantName"] = this.tenantName; + data["impersonatorUserId"] = this.impersonatorUserId; + data["impersonatorTenantId"] = this.impersonatorTenantId; + data["executionTime"] = this.executionTime ? this.executionTime.toISOString() : undefined; + data["executionDuration"] = this.executionDuration; + data["clientIpAddress"] = this.clientIpAddress; + data["clientName"] = this.clientName; + data["clientId"] = this.clientId; + data["correlationId"] = this.correlationId; + data["browserInfo"] = this.browserInfo; + data["httpMethod"] = this.httpMethod; + data["url"] = this.url; + data["exceptions"] = this.exceptions; + data["comments"] = this.comments; + data["httpStatusCode"] = this.httpStatusCode; + if (Array.isArray(this.entityChanges)) { + data["entityChanges"] = []; + for (let item of this.entityChanges) + data["entityChanges"].push(item.toJSON()); + } + if (Array.isArray(this.actions)) { + data["actions"] = []; + for (let item of this.actions) + data["actions"].push(item.toJSON()); + } + return data; + } +} + +export interface IQueryAuditLogOutput { + id?: string; + applicationName?: string | undefined; + userId?: string | undefined; + userName?: string | undefined; + tenantId?: string | undefined; + tenantName?: string | undefined; + impersonatorUserId?: string | undefined; + impersonatorTenantId?: string | undefined; + executionTime?: Date; + executionDuration?: number; + clientIpAddress?: string | undefined; + clientName?: string | undefined; + clientId?: string | undefined; + correlationId?: string | undefined; + browserInfo?: string | undefined; + httpMethod?: string | undefined; + url?: string | undefined; + exceptions?: string | undefined; + comments?: string | undefined; + httpStatusCode?: number | undefined; + entityChanges?: QueryEntityChangeOutput[] | undefined; + actions?: PropertyChangesDto[] | undefined; +} + +export class QueryAuditLogOutputPagedResultDto implements IQueryAuditLogOutputPagedResultDto { + items?: QueryAuditLogOutput[] | undefined; + totalCount?: number; + + constructor(data?: IQueryAuditLogOutputPagedResultDto) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + if (Array.isArray(_data["items"])) { + this.items = [] as any; + for (let item of _data["items"]) + this.items!.push(QueryAuditLogOutput.fromJS(item)); + } + this.totalCount = _data["totalCount"]; + } + } + + static fromJS(data: any): QueryAuditLogOutputPagedResultDto { + data = typeof data === 'object' ? data : {}; + let result = new QueryAuditLogOutputPagedResultDto(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + if (Array.isArray(this.items)) { + data["items"] = []; + for (let item of this.items) + data["items"].push(item.toJSON()); + } + data["totalCount"] = this.totalCount; + return data; + } +} + +export interface IQueryAuditLogOutputPagedResultDto { + items?: QueryAuditLogOutput[] | undefined; + totalCount?: number; +} + +export class QueryEntityChangeInput implements IQueryEntityChangeInput { + id?: string; + + constructor(data?: IQueryEntityChangeInput) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.id = _data["id"]; + } + } + + static fromJS(data: any): QueryEntityChangeInput { + data = typeof data === 'object' ? data : {}; + let result = new QueryEntityChangeInput(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["id"] = this.id; + return data; + } +} + +export interface IQueryEntityChangeInput { + id?: string; +} + +export class QueryEntityChangeOutput implements IQueryEntityChangeOutput { + id?: string; + auditLogId?: string; + tenantId?: string | undefined; + changeTime?: Date; + changeType?: EntityChangeType; + entityTenantId?: string | undefined; + entityId?: string | undefined; + entityTypeFullName?: string | undefined; + propertyChanges?: PropertyChangesDto[] | undefined; + extraProperties?: { [key: string]: any; } | undefined; + + constructor(data?: IQueryEntityChangeOutput) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.id = _data["id"]; + this.auditLogId = _data["auditLogId"]; + this.tenantId = _data["tenantId"]; + this.changeTime = _data["changeTime"] ? new Date(_data["changeTime"].toString()) : undefined; + this.changeType = _data["changeType"]; + this.entityTenantId = _data["entityTenantId"]; + this.entityId = _data["entityId"]; + this.entityTypeFullName = _data["entityTypeFullName"]; + if (Array.isArray(_data["propertyChanges"])) { + this.propertyChanges = [] as any; + for (let item of _data["propertyChanges"]) + this.propertyChanges!.push(PropertyChangesDto.fromJS(item)); + } + if (_data["extraProperties"]) { + this.extraProperties = {} as any; + for (let key in _data["extraProperties"]) { + if (_data["extraProperties"].hasOwnProperty(key)) + this.extraProperties![key] = _data["extraProperties"][key]; + } + } + } + } + + static fromJS(data: any): QueryEntityChangeOutput { + data = typeof data === 'object' ? data : {}; + let result = new QueryEntityChangeOutput(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["id"] = this.id; + data["auditLogId"] = this.auditLogId; + data["tenantId"] = this.tenantId; + data["changeTime"] = this.changeTime ? this.changeTime.toISOString() : undefined; + data["changeType"] = this.changeType; + data["entityTenantId"] = this.entityTenantId; + data["entityId"] = this.entityId; + data["entityTypeFullName"] = this.entityTypeFullName; + if (Array.isArray(this.propertyChanges)) { + data["propertyChanges"] = []; + for (let item of this.propertyChanges) + data["propertyChanges"].push(item.toJSON()); + } + if (this.extraProperties) { + data["extraProperties"] = {}; + for (let key in this.extraProperties) { + if (this.extraProperties.hasOwnProperty(key)) + data["extraProperties"][key] = this.extraProperties[key]; + } + } + return data; + } +} + +export interface IQueryEntityChangeOutput { + id?: string; + auditLogId?: string; + tenantId?: string | undefined; + changeTime?: Date; + changeType?: EntityChangeType; + entityTenantId?: string | undefined; + entityId?: string | undefined; + entityTypeFullName?: string | undefined; + propertyChanges?: PropertyChangesDto[] | undefined; + extraProperties?: { [key: string]: any; } | undefined; +} + export class RegisterDto implements IRegisterDto { readonly extraProperties?: { [key: string]: any; } | undefined; userName!: string; diff --git a/content/vue/src/views/admin/audits/AuditLog.vue b/content/vue/src/views/admin/audits/AuditLog.vue new file mode 100644 index 00000000..159780cf --- /dev/null +++ b/content/vue/src/views/admin/audits/AuditLog.vue @@ -0,0 +1,91 @@ + + + + diff --git a/content/vue/src/views/admin/audits/audit.ts b/content/vue/src/views/admin/audits/audit.ts new file mode 100644 index 00000000..22121b01 --- /dev/null +++ b/content/vue/src/views/admin/audits/audit.ts @@ -0,0 +1,296 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { useI18n } from '/@/hooks/web/useI18n'; +import moment from 'moment'; +import { AuditServiceProxy, QueryAuditLogInput, QueryAuditLogOutputPagedResultDto, QueryEntityChangeInput, QueryEntityChangeOutput } from '/@/services/ServiceProxies'; +const { t } = useI18n(); +export const searchFormSchema: FormSchema[] = [ + { + field: 'ExecutionTime', + component: 'DatePicker', + label: t('routes.admin.audit_executeTime'), + colProps: { + span: 3 + }, + componentProps: { + valueFormat: "YYYY-MM-DD" + } + }, + { + field: 'userName', + component: 'Input', + label: t('routes.admin.audit_userName'), + colProps: { + span: 4 + }, + }, + { + field: 'httpMethod', + component: 'Select', + label: t('routes.admin.audit_httpMethod'), + colProps: { + span: 4 + }, + componentProps: { + options: [ + { + label: 'GET', + value: 'GET', + key: '1', + }, { + label: 'HEAD', + value: 'HEAD', + key: '2', + }, { + label: 'POST', + value: 'POST', + key: '3', + }, { + label: 'PUT', + value: 'PUT', + key: '4', + }, { + label: 'DELETE', + value: 'DELETE', + key: '5', + }, { + label: 'CONNECT', + value: 'CONNET', + key: '6', + }, { + label: 'OPTIONS', + value: 'OPTIONS', + key: '7', + }, { + label: 'TRACE', + value: 'TRACE', + key: '8', + }, { + label: 'PATH', + value: 'PATH', + key: '9', + }, + ] + } + }, + { + field: 'httpStatusCode', + component: 'Select', + label: t('routes.admin.audit_httpStatusCode'), + colProps: { + span: 4 + }, + componentProps: { + options: [ + { + label: '100-Continue', + value: '100', + key: '1', + }, { + label: '101-SwitchingProtocols', + value: '101', + key: '2', + }, { + label: '200-OK', + value: '200', + key: '3', + }, { + label: '201-Created', + value: '201', + key: '4', + }, { + label: '202-Accepted', + value: '202', + key: '5', + }, { + label: '203-Non-AuthoritativeInformation', + value: '203', + key: '6', + }, { + label: '204-NoContent', + value: '204', + key: '7', + }, { + label: '205-ResetContent', + value: '205', + key: '8', + }, { + label: '206-PartialContent', + value: '206', + key: '9', + }, { + label: '300-MultipleChoices', + value: '300', + key: '10', + }, { + label: '301-MovedPermanently', + value: '301', + key: '11', + }, { + label: '302-Found', + value: '302', + key: '12', + }, { + label: '303-SeeOther', + value: '303', + key: '13', + }, { + label: '304-NotModified', + value: '304', + key: '14', + }, { + label: '305-UseProxy', + value: '305', + key: '15', + }, { + label: '306-Unused', + value: '306', + key: '16', + }, { + label: '307-TemporaryRedirect', + value: '307', + key: '17', + }, { + label: '400-BadRequest', + value: '400', + key: '18', + }, { + label: '401-Unauthorized', + value: '401', + key: '19', + }, { + label: '402-PaymentRequired', + value: '402', + key: '20', + }, { + label: '403-Forbidden', + value: '403', + key: '21', + }, { + label: '404-NotFound', + value: '404', + key: '22', + }, { + label: '405-MethodNotAllowed', + value: '405', + key: '23', + }, { + label: '406-NotAcceptable', + value: '406', + key: '24', + }, { + label: '407-ProxyAuthenticationRequired', + value: '407', + key: '25', + }, { + label: '408-RequestTime-out', + value: '408', + key: '26', + }, { + label: '409-Conflict', + value: '409', + key: '27', + }, { + label: '410-Gone', + value: '410', + key: '28', + }, { + label: '411-LengthRequired', + value: '411', + key: '29', + }, { + label: '412-PreconditionFailed', + value: '412', + key: '30', + }, { + label: '413-RequestEntityTooLarge', + value: '413', + key: '31', + }, { + label: '414-Request-URITooLarge', + value: '414', + key: '32', + }, { + label: '415-UnsupportedMediaType', + value: '415', + key: '33', + }, { + label: '416-Requestedrangenotsatisfiable', + value: '416', + key: '34', + }, { + label: '417-ExpectationFailed', + value: '417', + key: '35', + }, { + label: '500-InternalServerError', + value: '500', + key: '36', + }, { + label: '501-NotImplemented', + value: '501', + key: '37', + }, { + label: '502-BadGateway', + value: '502', + key: '38', + }, { + label: '503-ServiceUnavailable', + value: '503', + key: '39', + }, { + label: '504-GatewayTime-out', + value: '504', + key: '40', + }, { + label: '505-HTTPVersionnotsupported', + value: '505', + key: '41', + }, + + ] + } + } +] +export const tableColumns: BasicColumn[] = [ + { + title: t('routes.admin.audit_httpRequest'), + dataIndex: 'httpMethod', + slots: { customRender: 'category' } + }, + { + title: t('routes.admin.audit_url'), + dataIndex: 'url', + }, + { + title: t('routes.admin.audit_userName'), + dataIndex: 'userName', + }, + { + title: t('routes.admin.audit_ipAdrress'), + dataIndex: 'clientIpAddress', + }, + { + title: t('routes.admin.audit_executeTime'), + dataIndex: 'executionTime', + customRender: ({ text }) => { + return moment(text).format("YYYY-MM-DD HH:mm:ss"); + } + }, + { + title: t('routes.admin.audit_duration'), + dataIndex: 'executionDuration', + } +] +//获取表格列表 +export async function getTableListAsync(params: QueryAuditLogInput): Promise { + const _auditServiceProxy = new AuditServiceProxy(); + return await _auditServiceProxy.list(params); +} +//获取实体信息 +export async function getEntityInfoAsync(id: string): Promise { + + const _auditServiceProxy = new AuditServiceProxy(); + let request = new QueryEntityChangeInput(); + request.id = id; + return _auditServiceProxy.queryEntity(request); +} diff --git a/content/vue/src/views/admin/roles/AbpRole.ts b/content/vue/src/views/admin/roles/AbpRole.ts index ad095b70..6d7d7368 100644 --- a/content/vue/src/views/admin/roles/AbpRole.ts +++ b/content/vue/src/views/admin/roles/AbpRole.ts @@ -13,7 +13,6 @@ export const tableColumns: BasicColumn[] = [ { title: t('routes.admin.userManagement_roleName'), dataIndex: 'name', - }, { title: t('routes.admin.roleManagement_default'), diff --git a/content/vue/src/views/admin/users/AbpUser.ts b/content/vue/src/views/admin/users/AbpUser.ts index d08e0d82..91faffa2 100644 --- a/content/vue/src/views/admin/users/AbpUser.ts +++ b/content/vue/src/views/admin/users/AbpUser.ts @@ -6,7 +6,8 @@ import { UserServiceProxy, IdentityUserDtoPagedResultDto, IdentityRoleDtoListResultDto, - RoleServiceProxy + RoleServiceProxy, + LockUserInput } from '/@/services/ServiceProxies'; import { message } from 'ant-design-vue'; import { useLoading } from '/@/components/Loading'; @@ -39,6 +40,11 @@ export const tableColumns: BasicColumn[] = [ // dataIndex: 'phoneNumber', // }, + { + title: t('routes.admin.userManagement_locked'), + dataIndex: 'lockoutEnabled', + slots: { customRender: 'lockoutEnabled' } + }, { title: t('routes.admin.userManagement_createTime'), dataIndex: 'creationTime', @@ -214,3 +220,13 @@ export async function updateUserAsync({ request, changeOkLoading, validate, clos closeModal(); } + +/** + * 启用或者禁用用户 + * @param isLock + * @returns + */ +export async function lockUserAsync(request: LockUserInput): Promise { + const _userServiceProxy = new UserServiceProxy(); + return _userServiceProxy.lock(request); +} diff --git a/content/vue/src/views/admin/users/AbpUser.vue b/content/vue/src/views/admin/users/AbpUser.vue index 0f44b10c..4890bd3c 100644 --- a/content/vue/src/views/admin/users/AbpUser.vue +++ b/content/vue/src/views/admin/users/AbpUser.vue @@ -10,7 +10,11 @@ {{ t('common.createText') }} - + import { defineComponent } from 'vue'; import { BasicTable, useTable, TableAction } from '/@/components/Table'; - import { tableColumns, searchFormSchema, getTableListAsync, deleteUserAsync } from './AbpUser'; + import { + tableColumns, + searchFormSchema, + getTableListAsync, + deleteUserAsync, + lockUserAsync, + } from './AbpUser'; + import { LockUserInput } from '/@/services/ServiceProxies'; import { useModal } from '/@/components/Modal'; import CreateAbpUser from './CreateAbpUser.vue'; import EditAbpUser from './EditAbpUser.vue'; import { message } from 'ant-design-vue'; import { useI18n } from '/@/hooks/web/useI18n'; - + import { Tag } from 'ant-design-vue'; export default defineComponent({ name: 'AbpUser', components: { @@ -61,6 +81,7 @@ TableAction, CreateAbpUser, EditAbpUser, + Tag, }, setup() { const { t } = useI18n(); @@ -108,6 +129,13 @@ await deleteUserAsync({ userId: record.id, reload }); }; + const handleLock = async (record: Recordable) => { + let request = new LockUserInput(); + request.userId = record.id; + request.locked = !record.lockoutEnabled; + await lockUserAsync(request); + reload(); + }; return { registerTable, handleEdit, @@ -118,6 +146,7 @@ registerEditAbpUserModal, t, reload, + handleLock, }; }, }); diff --git a/content/vue/src/views/sys/login/Login.vue b/content/vue/src/views/sys/login/Login.vue index b47fb61a..b469e940 100644 --- a/content/vue/src/views/sys/login/Login.vue +++ b/content/vue/src/views/sys/login/Login.vue @@ -35,9 +35,6 @@ > - - - @@ -167,7 +164,7 @@ display: flex; width: 60%; height: 80px; - + margin-left: 85px; &__title { font-size: 24px; color: #fff; diff --git a/content/vue/yarn.lock b/content/vue/yarn.lock index db8e5e5d..1c8456e7 100644 --- a/content/vue/yarn.lock +++ b/content/vue/yarn.lock @@ -2555,6 +2555,15 @@ cli-width@^3.0.0: resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== +clipboard@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.8.tgz#ffc6c103dd2967a83005f3f61976aa4655a4cdba" + integrity sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ== + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -3295,6 +3304,11 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + depd@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -4577,6 +4591,13 @@ gonzales-pe@^4.3.0: dependencies: minimist "^1.2.5" +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= + dependencies: + delegate "^3.1.2" + got@^7.0.0: version "7.1.0" resolved "https://registry.npmjs.org/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" @@ -7789,6 +7810,11 @@ seek-bzip@^1.0.5: dependencies: commander "^2.8.1" +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -8650,6 +8676,11 @@ timed-out@^4.0.0, timed-out@^4.0.1: resolved "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= +tiny-emitter@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + tinycolor2@^1.4.2: version "1.4.2" resolved "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" @@ -9308,6 +9339,11 @@ vue-types@^3.0.0, vue-types@^3.0.2: dependencies: is-plain-object "3.0.1" +vue3-json-viewer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vue3-json-viewer/-/vue3-json-viewer-1.0.4.tgz#cbddcd2c0383315ef1f240f1d5db88e80e20c44a" + integrity sha512-E+FKQ8cGVXeZmS25AMeLlgNkTnewSy6Ex11udMn12ex06XcW3ZTgGfdTvg/oklh49B2pDZN1Osr94QOvgV8h5w== + vue@3.0.11, vue@^3.0.0: version "3.0.11" resolved "https://registry.npmjs.org/vue/-/vue-3.0.11.tgz#c82f9594cbf4dcc869241d4c8dd3e08d9a8f4b5f"