diff --git a/.github/instructions/lowcode-reference.instructions.md b/.github/instructions/lowcode-reference.instructions.md index 77be9968..308ca4c7 100644 --- a/.github/instructions/lowcode-reference.instructions.md +++ b/.github/instructions/lowcode-reference.instructions.md @@ -1250,7 +1250,7 @@ ekranın kendi yetki hatasını gösterir; ayrıca gizlemek istiyorsan `designer Gömülen görünüm, hedef ekranın `LayoutJson`'ında açık olmalıdır. **Public site düğümleri** yalnızca public bileşenlerde (`PublicHeader`, `PublicFooter`, public -sayfalar) anlamlıdır; veriyi uygulama konfigürasyonundan (`publicMenus`, tema, dil) okur: +sayfalar) anlamlıdır; veriyi uygulama konfigürasyonundan (`menus.public`, tema, dil) okur: | Düğüm | Prop'lar (varsayılan) | | --- | --- | @@ -1582,7 +1582,7 @@ Intranet de bu moduldedir: menü `Hr.Intranet`, ekran/yetki kodları `Hr.Intrane (Developer Kit › Intranet Widgets) Saas'ta kalır; kayıtlarının `PermissionName` değerleri `Hr.Intranet.*.Widget`'tır. İK yetkilerinin `Modules` değeri `Sas|Hr`'dir: host (`Sas`) her zaman görür, kiracı yalnızca modül -listesinde `Hr` varsa görür (modül listesi boş kiracıda süzme yoktur — `MenuAppService`, +listesinde `Hr` varsa görür (modül listesi boş kiracıda süzme yoktur — `MenuNavigationProvider`, `RolesPermission.tsx`). Yetki ağacı: `Hr.Definitions` → `Hr.SkillType` (1), `Hr.SkillLevel` (2), `Hr.Skill` (3), `Hr.Department` (4), `Hr.JobPosition` (5); `Hr.OrgChart` grubun kökünde (2). ListForm ekranlarının alt yetkileri `.Create`, `.Update`, @@ -1685,7 +1685,7 @@ yazma; tasarımcıda yeni bir Custom Component + `Pub` menü kaydı (`Pub.Header | Route | Bileşen | Veri | | --- | --- | --- | | `/home` · `/about` · `/contact` | Custom Component (`PublicHomePage` · `PublicAboutPage` · `PublicContactPage`) | `data/Sas.DeveloperKit.CustomComponents/{Id}.json` · SEO bileşen kaydında | -| (rotasız) | Custom Component (`PublicHeader` · `PublicFooter`) | Üst menü + alt bilgi hızlı bağlantıları: `Pub` menüsü `Pub.Header` (uygulama konfigürasyonunun `publicMenus` alanı) | +| (rotasız) | Custom Component (`PublicHeader` · `PublicFooter`) | Üst menü + alt bilgi hızlı bağlantıları: `Pub` menüsü `Pub.Header` (uygulama konfigürasyonunun `menus.public` alanı) | | `/products` · `/checkout` · `/payment` · `/success` | `views/public/{Products,Checkout,Payment,Success}` | `Sas_H_Product` · `Sas_H_PaymentMethod` · `Sas_H_InstallmentOption` · `Sas_H_Order` | | `/blog` · `/blog/:id` | `views/public/{Blog,BlogDetail}` | `Sas_H_BlogPost` · `Sas_H_BlogCategory` | | `/demo` | `views/public/Demo` | `Sas_H_Demo` | diff --git a/CLAUDE.md b/CLAUDE.md index 4d5b55bb..e52d0525 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -666,7 +666,7 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → silmek ya da yolunu değiştirmek çerçeve dışıdır. - **Public site** → menü `Menu` tablosunda `ModuleId = "Pub"` altında (`PlatformConsts.PublicSite`); `Pub.Header` hem üst menüyü hem alt bilgideki hızlı bağlantıları besler (ikisi de - `PublicNav` düğümü) ve ziyaretçiye uygulama konfigürasyonunun `publicMenus` alanıyla taşınır + `PublicNav` düğümü) ve ziyaretçiye uygulama konfigürasyonunun `menus.public` alanıyla taşınır (menü ucu `[Authorize]`). Sayfa ise Custom Component'tir: `/admin/` ile başlamayan `RoutePath` otomatik public route olur ve aynı yolu paylaşan fiziksel route'u gölgeler; `RoutePath` boş bırakılabilir (rotasız bileşen). Yeni public sayfa = diff --git a/README.md b/README.md index cf105ee2..afd9de69 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,9 @@ React/C# kodu yazmadan, yalnızca konfigürasyon ile mümkündür. ``` **Akışın özeti:** Kullanıcı giriş yapar → ABP `application-configuration` yanıtına -`PlatformApplicationConfigurationContributor` menü, route ve yetkileri ekler → `DynamicRouter` +`PlatformApplicationConfigurationContributor` menüleri (`extraProperties.menus`: `public` ziyaretçi +menüsü, `protected` kullanıcının yetkili yönetim navigasyonu — ayrı bir `/api/app/menu` isteği yoktur, +süzgeç `MenuNavigationProvider`), route ve yetkileri ekler → `DynamicRouter` route kayıtlarını React bileşenlerine bağlar → `/admin/list/{ListFormCode}` gibi bir ekran açıldığında `ListFormsAppService` metadata'yı, `ListFormDataAppService` ise veriyi döner → DevExtreme bileşenleri bu metadata'ya göre kendini kurar. @@ -988,7 +990,8 @@ Route.Authority ──┘ └► Permission `data/Sas.Menus.Module.json`): `Sas` → `Sas` (Saas), `Adm` → `Adm`, `Hr` → `Hr` (İK), `Pub` → `Pub.Header` (public site). Tablo ön eki aynı modülden gelir (`MenuPrefix`: `Sas_`, `Adm_`, `Hr_`; `TableNameResolver`). Her yetki satırı `Modules` -(pipe ile ayrılmış) değeri taşır; kiracının modül listesi doluysa menü (`MenuAppService`) ve rol +(pipe ile ayrılmış) değeri taşır; kiracının modül listesi doluysa menü (`MenuNavigationProvider`; +hem `menus.protected` hem Menü Yönetimi listesi bu süzgeçten geçer) ve rol yetki ekranı (`RolesPermission.tsx`) yalnızca kesişen modüllerin yetkilerini gösterir. Sözleşme: `Sas` host'ta görünürlüğü, `Adm` ve `Hr` kiracıdaki görünürlüğü belirler — Saas/Administration yetkileri `Sas|Adm`, İK yetkileri `Sas|Hr` taşır. @@ -1133,9 +1136,9 @@ menü bir kez eklenir iki yerde birden görünür; alt kaydı olan bir menü üs Alt bilgide `cta` kayıtları (Giriş) yer almaz: oradaki düğüm `source="navigation"` ile çizilir. `Url` sayfa yolu, `Icon` react-icons adı, `Order` sıra, `IsDisabled` yayın anahtarı, `DisplayName` dil anahtarıdır; `CssClass` içinde `cta` geçen kayıt düz bağlantı yerine vurgulu buton olarak çizilir. Menü ucu -`[Authorize]` olduğu için menüler uygulama konfigürasyonunun `publicMenus` alanıyla taşınır +`[Authorize]` olduğu için menüler uygulama konfigürasyonunun `menus.public` alanıyla taşınır (`PlatformApplicationConfigurationContributor`), istemcide `usePublicMenu` okur. `Pub` modülü -yönetim navigasyonuna girmez (`abpConfig.model.ts` menüyü kurarken bu modülü eler; Menü Yönetimi +yönetim navigasyonuna (`menus.protected`) girmez (`MenuNavigationProvider` bu modülü eler; Menü Yönetimi ekranı elemez, menüler oradan düzenlenir). Seed: `configs/seeds/data/Sas.Menus.Module.json` ve `Sas.Menus.Menu.json`. @@ -1954,6 +1957,9 @@ konfigürasyonu ile üretilir. - **easy-peasy** global store: `auth`, `theme`, `locale`, `abpConfig`, `admin`, `client`, `base`. Oturum ve tercih verileri `localStorage` yerine store üzerinden yönetilir (`stateSync.ts`). + Kenar çubuğu (`abpConfig.menu.mainMenu`) ayrı bir menü isteğiyle değil, `getConfig` içinde + application-configuration'ın `extraProperties.menus.protected` listesi + yüklü dil metinlerinden + kurulur; dil, yetki ya da menü kaydından sonra `getConfig` çağrısı menüyü de tazeler. - **React Query**: sunucu verisi önbellekleme. - **Context'ler**: `ComponentContext` (runtime bileşen kaydı), `DemoContext` (demo talebi diyaloğu). diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Menus/MenuNavigationDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Menus/MenuNavigationDto.cs new file mode 100644 index 00000000..55da28c7 --- /dev/null +++ b/api/src/Sozsoft.Platform.Application.Contracts/Menus/MenuNavigationDto.cs @@ -0,0 +1,17 @@ +namespace Sozsoft.Platform.Menus; + +/// +/// Yönetim navigasyonunun application-configuration ile taşınan menü satırı. Yalnızca +/// istemcinin ağacı kurmak için okuduğu alanları taşır; düzenleme ekranı kullanır. +/// +public class MenuNavigationDto +{ + public string Code { get; set; } + public string DisplayName { get; set; } + public int Order { get; set; } + public string Url { get; set; } + public string Icon { get; set; } + public string ParentCode { get; set; } + public string RequiredPermissionName { get; set; } + public string ModuleId { get; set; } +} diff --git a/api/src/Sozsoft.Platform.Application/Menu/MenuAppService.cs b/api/src/Sozsoft.Platform.Application/Menu/MenuAppService.cs index d778d14f..f8887257 100644 --- a/api/src/Sozsoft.Platform.Application/Menu/MenuAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Menu/MenuAppService.cs @@ -4,18 +4,14 @@ using System.Linq; using System.Linq.Dynamic.Core; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Logging; using Sozsoft.Languages.Caching; using Sozsoft.Languages.Entities; using Sozsoft.Languages.Languages; using Sozsoft.Platform.Entities; -using Sozsoft.Platform.Extensions; using Sozsoft.Platform.ListForms; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; -using Volo.Abp.PermissionManagement; -using Volo.Abp.TenantManagement; using static Sozsoft.Platform.Data.Seeds.SeedConsts; using Volo.Abp; @@ -31,8 +27,7 @@ public class MenuAppService : CrudAppService< private readonly IRepository _menuRepository; private readonly IRepository _repositoryKey; private readonly IRepository _repositoryText; - private readonly ITenantRepository _tenantRepository; - private readonly IPermissionDefinitionRecordRepository _permissionRepository; + private readonly MenuNavigationProvider _navigationProvider; private readonly DistributedCacheCleaner _cacheCleaner; private readonly IRepository _moduleRepository; private readonly IListFormSeedDataSynchronizer _seedDataSynchronizer; @@ -41,8 +36,7 @@ public class MenuAppService : CrudAppService< IRepository menuRepository, IRepository languageKeyRepository, IRepository languageTextRepository, - ITenantRepository tenantRepository, - IPermissionDefinitionRecordRepository permissionRepository, + MenuNavigationProvider navigationProvider, DistributedCacheCleaner cacheCleaner, IRepository moduleRepository, IListFormSeedDataSynchronizer seedDataSynchronizer @@ -51,8 +45,7 @@ public class MenuAppService : CrudAppService< _menuRepository = menuRepository; _repositoryKey = languageKeyRepository; _repositoryText = languageTextRepository; - _tenantRepository = tenantRepository; - _permissionRepository = permissionRepository; + _navigationProvider = navigationProvider; _cacheCleaner = cacheCleaner; _moduleRepository = moduleRepository; _seedDataSynchronizer = seedDataSynchronizer; @@ -68,34 +61,10 @@ public class MenuAppService : CrudAppService< var query = await CreateFilteredQueryAsync(input); query = query.Where(a => !a.IsDisabled); - - //Tenant üzerinden modül bilgisi alınıp yalnızca o modüllerin menüleri listelenecek - if (CurrentTenant.Id is Guid tenantId) - { - var tenant = await _tenantRepository.FindAsync(tenantId); - if (tenant != null) - { - var tenantModules = tenant.GetModules(); - if (tenantModules.Length > 0) - { - var permissionRecords = await _permissionRepository.GetListAsync(); - var allowedPermissionNames = permissionRecords - .Where(p => p.GetModules().Intersect(tenantModules).Any()) - .Select(p => p.Name) - .Distinct() - .ToList(); - - query = query.Where(menu => - string.IsNullOrEmpty(menu.RequiredPermissionName) || - allowedPermissionNames.Contains(menu.RequiredPermissionName)); - } - - } - } + query = await _navigationProvider.ApplyTenantModuleFilterAsync(query); var totalCount = await AsyncExecuter.CountAsync(query); - var entities = new List(); var entityDtos = new List(); if (totalCount > 0) @@ -103,38 +72,7 @@ public class MenuAppService : CrudAppService< query = ApplySorting(query, input); query = ApplyPaging(query, input); - var items = await AsyncExecuter.ToListAsync(query); - - // Tüm unique permission'ları topla - var uniquePermissions = items - .Where(x => !string.IsNullOrWhiteSpace(x.RequiredPermissionName)) - .Select(x => x.RequiredPermissionName) - .Distinct() - .ToList(); - - // Tüm permission'ları bir kerede kontrol et (N+1 query problemini önler) - var grantedPermissions = new HashSet(); - foreach (var permission in uniquePermissions) - { - try - { - if (await AuthorizationService.IsGrantedAsync(permission)) - { - grantedPermissions.Add(permission); - } - } - catch (Exception ex) - { - Logger.LogError(ex, $"Permission error for {permission}: {ex.Message}"); - } - } - - // Sadece yetkili menüleri filtrele - entities = items - .Where(item => string.IsNullOrWhiteSpace(item.RequiredPermissionName) || - grantedPermissions.Contains(item.RequiredPermissionName)) - .ToList(); - + var entities = await _navigationProvider.FilterGrantedAsync(await AsyncExecuter.ToListAsync(query)); entityDtos = await base.MapToGetListOutputDtosAsync(entities); var languageKeys = entityDtos @@ -291,22 +229,6 @@ public class MenuAppService : CrudAppService< await _seedDataSynchronizer.RemoveRowsAsync(AppCodes.Menus.Menu, [menu.Code]); } - public async Task> GetListMainMenuAsync() - { - await CheckGetPolicyAsync(); - - var query = await _menuRepository.GetQueryableAsync(); - // Public site menusu yonetim kenar cubugunda yer almaz; o agac ziyaretci - // duzenine aittir ve uygulama konfigurasyonu uzerinden tasinir. - query = query.Where(a => !a.IsDisabled && a.ModuleId != null && - a.ModuleId != PlatformConsts.PublicSite.ModuleId); - - var entities = await AsyncExecuter.ToListAsync(query); - var entityDtos = await MapToGetListOutputDtosAsync(entities); - - return entityDtos; - } - /// /// Menüyü tüm alanlarıyla günceller ve görünen adın dil metinlerini de yazar. /// Kod (Code) dil anahtarı ve yetki eşleşmesi olduğu için değiştirilemez; gönderilen diff --git a/api/src/Sozsoft.Platform.Application/Menu/MenuNavigationProvider.cs b/api/src/Sozsoft.Platform.Application/Menu/MenuNavigationProvider.cs new file mode 100644 index 00000000..a6266579 --- /dev/null +++ b/api/src/Sozsoft.Platform.Application/Menu/MenuNavigationProvider.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sozsoft.Platform.Entities; +using Sozsoft.Platform.Extensions; +using Volo.Abp.Authorization; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Linq; +using Volo.Abp.MultiTenancy; +using Volo.Abp.PermissionManagement; +using Volo.Abp.TenantManagement; + +namespace Sozsoft.Platform.Menus; + +/// +/// Oturum açık kullanıcının görebileceği menüleri süzer: kiracının modül listesi dışındaki +/// yetkiye bağlı menü ve kullanıcıya verilmemiş yetkili menü elenir. Yönetim navigasyonu +/// (application-configuration `menus`) ve menü düzenleme listesi aynı süzgeçten geçer; iki +/// kopya olsaydı kenar çubuğu ile düzenleme ekranı farklı menü gösterirdi. +/// +public class MenuNavigationProvider( + IRepository menuRepository, + ITenantRepository tenantRepository, + IPermissionDefinitionRecordRepository permissionRepository, + ICurrentTenant currentTenant, + IAuthorizationService authorizationService, + IAsyncQueryableExecuter asyncExecuter, + ILogger logger) : ITransientDependency +{ + /// Yönetim kenar çubuğunun menüleri: pasifler ve public site ağacı dışarıda, sıra `Order`. + public async Task> GetNavigationAsync() + { + var query = (await menuRepository.GetQueryableAsync()) + .Where(menu => !menu.IsDisabled && menu.ModuleId != null && + menu.ModuleId != PlatformConsts.PublicSite.ModuleId); + query = await ApplyTenantModuleFilterAsync(query); + + var menus = await FilterGrantedAsync(await asyncExecuter.ToListAsync(query.OrderBy(menu => menu.Order))); + + return menus.Select(menu => new MenuNavigationDto + { + Code = menu.Code, + DisplayName = menu.DisplayName, + Order = menu.Order, + Url = menu.Url, + Icon = menu.Icon, + ParentCode = menu.ParentCode, + RequiredPermissionName = menu.RequiredPermissionName, + ModuleId = menu.ModuleId + }).ToList(); + } + + /// + /// Kiracının modül listesi doluysa yalnızca o modüllerin yetkisine bağlı (ya da yetkisiz) + /// menüler kalır; liste boş kiracıda süzme yoktur. + /// + public async Task> ApplyTenantModuleFilterAsync(IQueryable query) + { + if (currentTenant.Id is not Guid tenantId) + { + return query; + } + + var tenant = await tenantRepository.FindAsync(tenantId); + var tenantModules = tenant?.GetModules() ?? []; + if (tenantModules.Length == 0) + { + return query; + } + + var allowedPermissionNames = (await permissionRepository.GetListAsync()) + .Where(permission => permission.GetModules().Intersect(tenantModules).Any()) + .Select(permission => permission.Name) + .Distinct() + .ToList(); + + return query.Where(menu => + string.IsNullOrEmpty(menu.RequiredPermissionName) || + allowedPermissionNames.Contains(menu.RequiredPermissionName)); + } + + /// Yetki gerektiren menülerden yalnızca kullanıcıya verilmiş olanları bırakır; her yetki bir kez sorulur. + public async Task> FilterGrantedAsync(IReadOnlyCollection menus) + { + var grantedPermissions = new HashSet(); + foreach (var permission in menus + .Select(menu => menu.RequiredPermissionName) + .Where(name => !string.IsNullOrWhiteSpace(name)) + .Distinct()) + { + try + { + if (await authorizationService.IsGrantedAsync(permission)) + { + grantedPermissions.Add(permission); + } + } + catch (Exception ex) + { + // Tanımı silinmiş bir yetkiye bağlı menü navigasyonu düşürmemeli; menü gizlenir. + logger.LogError(ex, "Permission check failed for menu permission {Permission}", permission); + } + } + + return menus + .Where(menu => string.IsNullOrWhiteSpace(menu.RequiredPermissionName) || + grantedPermissions.Contains(menu.RequiredPermissionName)) + .ToList(); + } +} diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/PlatformApplicationConfigurationContributor.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/PlatformApplicationConfigurationContributor.cs index af9e7a08..b93e542e 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/PlatformApplicationConfigurationContributor.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/PlatformApplicationConfigurationContributor.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Sozsoft.Platform.Entities; using Sozsoft.Platform.Identity; +using Sozsoft.Platform.Menus; using Sozsoft.Platform.Public; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.Data; @@ -54,13 +55,20 @@ public class PlatformApplicationConfigurationContributor : IApplicationConfigura context.ApplicationConfiguration.SetProperty( "customComponents", await customComponentRepository.GetListAsync()); - // Public site menusu: ziyaretci menu ucunu cagiramaz ([Authorize]), bu yuzden - // rotalar ve custom component'ler gibi anonim erisilen konfigurasyonla tasinir. - var menuRepository = context.ServiceProvider.GetRequiredService>(); - var publicMenus = await menuRepository.GetListAsync( - menu => menu.ModuleId == PlatformConsts.PublicSite.ModuleId && !menu.IsDisabled); + // Public sayfanin metasi ilk boyamada hazir olmali; ayri bir istek beklenirse + // tarayici basligi ve paylasim karti bir kare gec dolar. + var seoAppService = context.ServiceProvider.GetRequiredService(); + context.ApplicationConfiguration.SetProperty("seo", await seoAppService.GetConfigAsync()); - context.ApplicationConfiguration.SetProperty("publicMenus", publicMenus + var currentUser = context.ServiceProvider.GetRequiredService(); + + // Menüler tek başlıkta taşınır: `public` ziyaretçi menüsüdür (menü ucu [Authorize] + // olduğu için ayrı istek atamaz), `protected` oturum açık kullanıcının yönetim navigasyonudur. + // İstemci dil/yetki değişiminde konfigürasyonu zaten yeniden okur; ayrı bir menü isteği + // aynı veriyi ikinci kez taşırdı. + var menuRepository = context.ServiceProvider.GetRequiredService>(); + var publicMenus = (await menuRepository.GetListAsync( + menu => menu.ModuleId == PlatformConsts.PublicSite.ModuleId && !menu.IsDisabled)) .OrderBy(menu => menu.Order) .Select(menu => new { @@ -74,14 +82,12 @@ public class PlatformApplicationConfigurationContributor : IApplicationConfigura target = menu.Target, requiredPermissionName = menu.RequiredPermissionName }) - .ToList()); + .ToList(); + var protectedMenus = currentUser.Id.HasValue + ? await context.ServiceProvider.GetRequiredService().GetNavigationAsync() + : []; + context.ApplicationConfiguration.SetProperty("menus", new { @public = publicMenus, @protected = protectedMenus }); - // Public sayfanin metasi ilk boyamada hazir olmali; ayri bir istek beklenirse - // tarayici basligi ve paylasim karti bir kare gec dolar. - var seoAppService = context.ServiceProvider.GetRequiredService(); - context.ApplicationConfiguration.SetProperty("seo", await seoAppService.GetConfigAsync()); - - var currentUser = context.ServiceProvider.GetRequiredService(); if (currentUser.Id.HasValue) { var sessionRepository = context.ServiceProvider.GetRequiredService(); diff --git a/ui/src/proxy/menus/models.ts b/ui/src/proxy/menus/models.ts index 9e9223fa..e7c8a52e 100644 --- a/ui/src/proxy/menus/models.ts +++ b/ui/src/proxy/menus/models.ts @@ -1,4 +1,5 @@ import { FullAuditedEntityDto } from '../abp' +import type { PublicMenuItem } from '@/utils/hooks/usePublicMenu' export interface MenuDto extends FullAuditedEntityDto { code?: string @@ -19,3 +20,15 @@ export interface MenuDto extends FullAuditedEntityDto { menuTextTr?: string menuTextEn?: string } + +/** application-configuration `extraProperties.menus.protected` satırı: yönetim navigasyonunu kuran alanlar. */ +export type MenuNavigationDto = Pick< + MenuDto, + 'code' | 'displayName' | 'order' | 'url' | 'icon' | 'parentCode' | 'requiredPermissionName' | 'moduleId' +> + +/** application-configuration `extraProperties.menus`: ziyaretçi menüsü + oturum açık kullanıcının navigasyonu. */ +export interface ConfigMenus { + public: PublicMenuItem[] + protected: MenuNavigationDto[] +} diff --git a/ui/src/services/menu.service.ts b/ui/src/services/menu.service.ts index e4428c1f..8c36baa9 100644 --- a/ui/src/services/menu.service.ts +++ b/ui/src/services/menu.service.ts @@ -94,15 +94,6 @@ export class MenuService { { apiName: this.apiName }, ) - getListMainMenu = () => - apiService.fetchData( - { - method: 'GET', - url: `/api/app/menu/main-menu`, - }, - { apiName: this.apiName }, - ) - getModules = () => apiService.fetchData( { diff --git a/ui/src/store/abpConfig.model.ts b/ui/src/store/abpConfig.model.ts index 55a94cd5..03526ccc 100644 --- a/ui/src/store/abpConfig.model.ts +++ b/ui/src/store/abpConfig.model.ts @@ -1,12 +1,11 @@ import type { Action, Thunk } from 'easy-peasy' import { action, thunk } from 'easy-peasy' -import { Injections, StoreModel } from '.' +import { Injections } from '.' import { getLocalization, setLocalization } from '../services/localization.service' import appConfig from '../proxy/configs/app.config' import { NavigationTree } from '../proxy/menus/navigation' -import { MenuDto } from '../proxy/menus/models' +import { ConfigMenus, MenuNavigationDto } from '../proxy/menus/models' import getChildren from '../utils/navigation' -import { PUBLIC_MENU_MODULE_ID } from '../utils/hooks/usePublicMenu' import { ApplicationConfigurationDto, ApplicationLocalizationRequestDto, @@ -30,6 +29,23 @@ const getWarmupRoutes = (routes: RouteDto[] | undefined, granted: Record route.componentPath.replace(/^@\//, '')) +/** + * Yönetim kenar çubuğu, application-configuration'ın `extraProperties.menus.protected` alanından kurulur; + * sunucu pasif, public ve yetkisiz menüleri zaten elemiştir. Görünen ad etkin dilin metnidir. + */ +const buildMainMenu = ( + menus: MenuNavigationDto[] | undefined, + texts: Texts | undefined, + defaultResourceName: string | undefined, +): NavigationTree[] => + getChildren( + (menus ?? []).map((menu) => ({ + ...menu, + displayName: getLocalization(texts ?? {}, defaultResourceName, '::' + menu.displayName), + })), + null, + ) + export interface GetConfigOptions { /** Localization kaynaklarını da zorla yeniden yükle. */ reloadTexts?: boolean @@ -51,7 +67,6 @@ export interface AbpConfigModel { mainMenu: NavigationTree[] } setMenu: Action - getMenu: Thunk } export const abpConfigModel: AbpConfigModel = { @@ -98,10 +113,15 @@ export const abpConfigModel: AbpConfigModel = { : [], ) - //Eğer login değilse - if (result.data.currentUser.isAuthenticated) { - await actions.getMenu() - } + actions.setMenu( + result.data.currentUser.isAuthenticated + ? buildMainMenu( + (result.data.extraProperties?.['menus'] as ConfigMenus | undefined)?.protected, + helpers.getState().texts, + result.data.localization.defaultResourceName, + ) + : [], + ) }), texts: undefined, setTexts: action((state, payload) => { @@ -118,34 +138,4 @@ export const abpConfigModel: AbpConfigModel = { setMenu: action((state, payload) => { state.menu.mainMenu = [...payload] }), - getMenu: thunk(async (actions, _, { injections, getState, getStoreState }) => { - const { signedIn } = getStoreState().auth.session - if (!signedIn) { - return - } - - const result = await injections.menuService.getList( - { - sorting: 'order', - skipCount: 0, - maxResultCount: 1000, - } - ) - - const texts = getState().texts - const defaultResourceName = getState().config?.localization.defaultResourceName - - // Public site menusu ziyaretci duzenine aittir; yonetim navigasyonunda yer almaz. - const items = (result.data.items ?? []).filter( - (menu: MenuDto) => menu.moduleId !== PUBLIC_MENU_MODULE_ID, - ) - - items.forEach((menu: MenuDto) => { - const displayName = '::' + menu.displayName - menu.displayName = getLocalization(texts || {}, defaultResourceName, displayName) - }) - - const menu = getChildren(items, null) - actions.setMenu(menu) - }), } diff --git a/ui/src/store/store.ts b/ui/src/store/store.ts index cba951f6..2d0acc92 100644 --- a/ui/src/store/store.ts +++ b/ui/src/store/store.ts @@ -9,7 +9,6 @@ import { ClientModel, clientModel } from './client.model' import { LocaleModel, localeModel } from './locale.model' import { ThemeModel, themeModel } from './theme.model' import { refreshToken } from '../services/auth.service' -import { MenuService } from '../services/menu.service' import { initStateSync, stateSyncMiddleware } from './stateSync' export interface StoreModel { @@ -22,11 +21,8 @@ export interface StoreModel { admin: AdminModel } -const menuService = new MenuService() - const injections = { abpConfigService, - menuService, listFormService, refreshToken, } diff --git a/ui/src/utils/hooks/usePublicMenu.ts b/ui/src/utils/hooks/usePublicMenu.ts index 5b7d90ce..d897cb85 100644 --- a/ui/src/utils/hooks/usePublicMenu.ts +++ b/ui/src/utils/hooks/usePublicMenu.ts @@ -1,9 +1,10 @@ import { useMemo } from 'react' import { useStoreState } from '@/store' +import type { ConfigMenus } from '@/proxy/menus/models' /** * Public site menu kaydi. Menuler `Pub` modulu altinda tutulur ve uygulama - * konfigurasyonunun `publicMenus` alaniyla tasinir; menu ucu `[Authorize]` + * konfigurasyonunun `menus.public` alaniyla tasinir; menu ucu `[Authorize]` * oldugu icin ziyaretci ayri bir istek atamaz. */ export interface PublicMenuItem { @@ -25,9 +26,6 @@ export interface PublicMenuNode extends PublicMenuItem { /** Ust menude bu sinifi tasiyan kayit duz baglanti yerine vurgulu buton olarak cizilir. */ export const PUBLIC_MENU_CTA_CLASS = 'cta' -/** Public site menulerinin modulu; yonetim navigasyonu bu modulu disarida birakir. */ -export const PUBLIC_MENU_MODULE_ID = 'Pub' - /** Ust ve alt bilgiyi cizen Custom Component'lerin adlari. */ export const PUBLIC_HEADER_COMPONENT = 'PublicHeader' export const PUBLIC_FOOTER_COMPONENT = 'PublicFooter' @@ -51,8 +49,8 @@ function buildChildren( */ export function usePublicMenu() { const publicMenus = useStoreState( - (state) => state.abpConfig.config?.extraProperties?.['publicMenus'], - ) as PublicMenuItem[] | undefined + (state) => (state.abpConfig.config?.extraProperties?.['menus'] as ConfigMenus | undefined)?.public, + ) const grantedPolicies = useStoreState((state) => state.abpConfig.config?.auth?.grantedPolicies) return useMemo(() => { diff --git a/ui/src/utils/navigation.ts b/ui/src/utils/navigation.ts index e1a09d18..a9bd8de0 100644 --- a/ui/src/utils/navigation.ts +++ b/ui/src/utils/navigation.ts @@ -4,7 +4,7 @@ import { NAV_ITEM_TYPE_ITEM, NAV_ITEM_TYPE_TITLE, } from '@/constants/navigation.constant' -import { MenuDto } from '@/proxy/menus/models' +import { MenuNavigationDto } from '@/proxy/menus/models' export function navigationTreeToFlat(items: NavigationTree[]): NavigationTree[] { const result: NavigationTree[] = [] @@ -24,7 +24,7 @@ export function navigationTreeToFlat(items: NavigationTree[]): NavigationTree[] return result } -export default function getChildren(menu: MenuDto[], parentCode: string | null): NavigationTree[] { +export default function getChildren(menu: MenuNavigationDto[], parentCode: string | null): NavigationTree[] { const menus: NavigationTree[] = [] for (const child of menu.filter((a) => a.parentCode === parentCode)) { const item: NavigationTree = {