From 4393a24d7c14ef964e34d624fdee825c36f4847f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C3=87A=C4=9EDA=C5=9E?= Date: Thu, 15 Jan 2026 16:51:16 +0300 Subject: [PATCH] created feature management module pages for MudBlazor --- .../AbpFeatureManagementComponentBase.cs | 12 + .../Components/FeatureManagementModal.razor | 90 ++++++ .../FeatureManagementModal.razor.cs | 263 ++++++++++++++++++ .../FeatureSettingManagementComponent.razor | 19 ++ ...FeatureSettingManagementComponent.razor.cs | 41 +++ .../FeatureSettingViewModel.cs | 6 + ...reSettingManagementComponentContributor.cs | 41 +++ .../_Imports.razor | 4 + 8 files changed, 476 insertions(+) create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/AbpFeatureManagementComponentBase.cs create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor.cs create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor.cs create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingViewModel.cs create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Settings/FeatureSettingManagementComponentContributor.cs create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/_Imports.razor diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/AbpFeatureManagementComponentBase.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/AbpFeatureManagementComponentBase.cs new file mode 100644 index 0000000000..68ed6fc8fc --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/AbpFeatureManagementComponentBase.cs @@ -0,0 +1,12 @@ +using Volo.Abp.AspNetCore.Components; +using Volo.Abp.FeatureManagement.Localization; + +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor; + +public abstract class AbpFeatureManagementComponentBase : AbpComponentBase +{ + protected AbpFeatureManagementComponentBase() + { + LocalizationResource = typeof(AbpFeatureManagementResource); + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor new file mode 100644 index 0000000000..f76e8a9630 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor @@ -0,0 +1,90 @@ +@using Volo.Abp.Validation.StringValues +@using Microsoft.Extensions.Localization +@using MudBlazor +@using Volo.Abp.FeatureManagement.Localization +@inherits AbpFeatureManagementComponentBase + + + + @L["Features"]@ProviderKeyDisplayName + + + @if (Groups == null || !Groups.Any()) + { + @L["NoFeatureFoundMessage"] + } + else + { + + @foreach (var group in Groups) + { + + @group.DisplayName + + @foreach (var feature in group.Features) + { + var disabled = IsDisabled(feature); +
+ @if (feature.ValueType is FreeTextStringValueType) + { + + @if (feature.Description != null) + { + @feature.Description + } + } + + @if (feature.ValueType is SelectionStringValueType) + { + var items = ((SelectionStringValueType)feature.ValueType).ItemSource.Items; + var selectedValue = SelectionStringValues[feature.Name]; + + @foreach (var item in items) + { + + @CreateStringLocalizer(item.DisplayText.ResourceName).GetString(item.DisplayText.Name) + + } + + @if (feature.Description != null) + { + @feature.Description + } + } + + @if (feature.ValueType is ToggleStringValueType) + { + + @if (feature.Description != null) + { + @feature.Description + } + } +
+ } +
+ } +
+ } +
+ + @L["Cancel"] + @L["ResetToDefault"] + @L["Save"] + +
diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor.cs new file mode 100644 index 0000000000..a6beb96acb --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Messages; +using Volo.Abp.AspNetCore.Components.Web.Configuration; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor; +using Volo.Abp.Features; +using Volo.Abp.Localization; +using Volo.Abp.Validation.StringValues; + +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components; + +public partial class FeatureManagementModal +{ + [Inject] protected IFeatureAppService FeatureAppService { get; set; } = default!; + + [Inject] protected IUiMessageService UiMessageService { get; set; } = default!; + + [Inject] protected IStringLocalizerFactory HtmlLocalizerFactory { get; set; } = default!; + + [Inject] protected IOptions LocalizationOptions { get; set; } = default!; + + [Inject] protected ICurrentApplicationConfigurationCacheResetService CurrentApplicationConfigurationCacheResetService { get; set; } = default!; + + [Inject] protected ISnackbar Snackbar { get; set; } = default!; + + [Inject] protected IDialogService DialogService { get; set; } = default!; + + protected bool _isVisible; + protected int _activeTabIndex = 0; + + protected string? ProviderName; + protected string? ProviderKey; + protected string? ProviderKeyDisplayName; + + protected List? Groups { get; set; } + + protected Dictionary ToggleValues = new(); + + protected Dictionary SelectionStringValues = new(); + + public virtual async Task OpenAsync(string providerName, string? providerKey = null, string? providerKeyDisplayName = null) + { + try + { + ProviderName = providerName; + ProviderKey = providerKey; + + ToggleValues = new Dictionary(); + SelectionStringValues = new Dictionary(); + + if (!providerKeyDisplayName.IsNullOrWhiteSpace()) + { + ProviderKeyDisplayName = $" - {providerKeyDisplayName}"; + } + else + { + ProviderKeyDisplayName = string.Empty; + } + + var result = await FeatureAppService.GetAsync(ProviderName, ProviderKey); + + Groups = result?.Groups ?? new List(); + + if (Groups.Any()) + { + _activeTabIndex = 0; + } + + foreach (var featureGroupDto in Groups) + { + foreach (var featureDto in featureGroupDto.Features) + { + if (featureDto.ValueType is ToggleStringValueType) + { + ToggleValues.Add(featureDto.Name, bool.Parse(featureDto.Value)); + } + + if (featureDto.ValueType is SelectionStringValueType) + { + SelectionStringValues.Add(featureDto.Name, featureDto.Value); + } + } + } + + _isVisible = true; + await InvokeAsync(StateHasChanged); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + public virtual Task CloseModal() + { + _isVisible = false; + return InvokeAsync(StateHasChanged); + } + + protected virtual async Task SaveAsync() + { + try + { + if (Groups == null) + { + return; + } + + var features = new UpdateFeaturesDto + { + Features = Groups.SelectMany(g => g.Features).Select(f => new UpdateFeatureDto + { + Name = f.Name, + Value = f.ValueType is ToggleStringValueType ? ToggleValues[f.Name].ToString() : + f.ValueType is SelectionStringValueType ? SelectionStringValues[f.Name] : f.Value + }).ToList() + }; + + await FeatureAppService.UpdateAsync(ProviderName!, ProviderKey, features); + + await CurrentApplicationConfigurationCacheResetService.ResetAsync(); + + _isVisible = false; + await InvokeAsync(StateHasChanged); + Snackbar.Add(L["SavedSuccessfully"], Severity.Success); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + public virtual async Task DeleteAsync(string providerName, string? providerKey = null) + { + try + { + var confirmed = await DialogService.ShowMessageBox( + L["AreYouSure"], + L["AreYouSureToResetToDefault"], + yesText: L["Yes"], + cancelText: L["Cancel"]); + + if (confirmed != true) + { + return; + } + + await FeatureAppService.DeleteAsync(ProviderName!, ProviderKey); + Snackbar.Add(L["ResetedToDefault"], Severity.Success); + + await CloseModal(); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual string GetNormalizedGroupName(string name) + { + return "FeatureGroup_" + name.Replace(".", "_"); + } + + protected virtual string GetFeatureStyles(FeatureDto feature) + { + return $"margin-left: {feature.Depth * 20}px"; + } + + protected virtual bool IsDisabled(FeatureDto feature) + { + return feature.Value != null && + feature.Provider.Name != null && + feature.Provider.Name != ProviderName && + feature.Provider.Name != DefaultValueFeatureValueProvider.ProviderName; + } + + public virtual string GetShownName(FeatureDto featureDto) + { + return !IsDisabled(featureDto) + ? featureDto.DisplayName + : $"{featureDto.DisplayName} ({featureDto.Provider.Name})"; + } + + protected virtual async Task OnFeatureValueChangedAsync(string? value, FeatureDto feature) + { + if (feature?.ValueType?.Validator.IsValid(value) == true) + { + feature.Value = value; + } + else + { + await UiMessageService.Warn(L["Volo.Abp.FeatureManagement:InvalidFeatureValue", feature.DisplayName]); + } + } + + protected virtual Task OnSelectedValueChangedAsync(bool value, FeatureDto feature) + { + ToggleValues[feature.Name] = value; + + if (value) + { + CheckParents(feature.ParentName); + } + else + { + UncheckChildren(feature.Name); + } + + return Task.CompletedTask; + } + + protected virtual void CheckParents(string? parentName) + { + if (parentName.IsNullOrWhiteSpace() || Groups == null) + { + return; + } + + foreach (var featureGroupDto in Groups) + { + foreach (var featureDto in featureGroupDto.Features) + { + if (featureDto.Name == parentName && ToggleValues.ContainsKey(featureDto.Name)) + { + ToggleValues[featureDto.Name] = true; + CheckParents(featureDto.ParentName); + } + } + } + } + + protected virtual void UncheckChildren(string featureName) + { + if (Groups == null) + { + return; + } + + foreach (var featureGroupDto in Groups) + { + foreach (var featureDto in featureGroupDto.Features) + { + if (featureDto.ParentName == featureName && ToggleValues.ContainsKey(featureDto.Name)) + { + ToggleValues[featureDto.Name] = false; + UncheckChildren(featureDto.Name); + } + } + } + } + + protected virtual IStringLocalizer CreateStringLocalizer(string resourceName) + { + return HtmlLocalizerFactory.CreateByResourceNameOrNull(resourceName) ?? + HtmlLocalizerFactory.CreateDefaultOrNull(); + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor new file mode 100644 index 0000000000..2c763e9660 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor @@ -0,0 +1,19 @@ +@using MudBlazor +@using Volo.Abp.FeatureManagement.Localization +@inherits Volo.Abp.AspNetCore.Components.AbpComponentBase + +@if (Settings != null && Settings.HasManageHostFeaturesPermission) +{ + + @L["ManageHostFeaturesText"] + + @L["ManageHostFeatures"] + + + + +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor.cs new file mode 100644 index 0000000000..8c04c6868d --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components; +using Volo.Abp.AspNetCore.Components; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components; +using Volo.Abp.FeatureManagement.Localization; +using Volo.Abp.Features; + +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components.FeatureSettingGroup; + +public partial class FeatureSettingManagementComponent : AbpComponentBase +{ + [Inject] + protected PermissionChecker PermissionChecker { get; set; } = default!; + + protected FeatureManagementModal? FeatureManagementModal; + + protected FeatureSettingViewModel? Settings; + + public FeatureSettingManagementComponent() + { + LocalizationResource = typeof(AbpFeatureManagementResource); + } + + protected async override Task OnInitializedAsync() + { + Settings = new FeatureSettingViewModel + { + HasManageHostFeaturesPermission = await AuthorizationService.IsGrantedAsync(FeatureManagementPermissions.ManageHostFeatures) + }; + } + + protected virtual async Task OnManageHostFeaturesClicked() + { + if (FeatureManagementModal != null) + { + await FeatureManagementModal.OpenAsync(TenantFeatureValueProvider.ProviderName); + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingViewModel.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingViewModel.cs new file mode 100644 index 0000000000..b933647b73 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingViewModel.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components.FeatureSettingGroup; + +public class FeatureSettingViewModel +{ + public bool HasManageHostFeaturesPermission { get; set; } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Settings/FeatureSettingManagementComponentContributor.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Settings/FeatureSettingManagementComponentContributor.cs new file mode 100644 index 0000000000..50bec2f38c --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Settings/FeatureSettingManagementComponentContributor.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components.FeatureSettingGroup; +using Volo.Abp.FeatureManagement.Localization; +using Volo.Abp.SettingManagement.Blazor.MudBlazor; + +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor.Settings; + +public class FeatureSettingManagementComponentContributor: ISettingComponentContributor +{ + public virtual async Task ConfigureAsync(SettingComponentCreationContext context) + { + if (!await CheckPermissionsInternalAsync(context)) + { + return; + } + + var l = context.ServiceProvider.GetRequiredService>(); + context.Groups.Add( + new SettingComponentGroup( + "Volo.Abp.Feature", + l["Permission:FeatureManagement"], + typeof(FeatureSettingManagementComponent) + ) + ); + } + + public virtual async Task CheckPermissionsAsync(SettingComponentCreationContext context) + { + return await CheckPermissionsInternalAsync(context); + } + + protected virtual async Task CheckPermissionsInternalAsync(SettingComponentCreationContext context) + { + var authorizationService = context.ServiceProvider.GetRequiredService(); + + return await authorizationService.IsGrantedAsync(FeatureManagementPermissions.ManageHostFeatures); + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/_Imports.razor b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/_Imports.razor new file mode 100644 index 0000000000..4e91324cd8 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/_Imports.razor @@ -0,0 +1,4 @@ +@using Microsoft.AspNetCore.Components.Web +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor +@using MudBlazor +@using Volo.Abp.MudBlazorUI