ListForm Wizard Manager
This commit is contained in:
parent
503c45282b
commit
a72faa083c
18 changed files with 999 additions and 59 deletions
|
|
@ -1,9 +1,12 @@
|
||||||
using System.Threading.Tasks;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
public interface IListFormWizardAppService
|
public interface IListFormWizardAppService
|
||||||
{
|
{
|
||||||
Task Create(ListFormWizardDto input);
|
Task Create(ListFormWizardDto input);
|
||||||
|
Task<List<WizardFileInfoDto>> GetFiles();
|
||||||
|
Task DeleteFile(string fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wizard seed dosyası formatı.
|
||||||
|
/// WizardAppService tarafından Seeds/WizardData/{wizardName}.json olarak kaydedilir.
|
||||||
|
/// WizardDataSeeder (DbMigrator) bu dosyaları okuyarak veritabanını yeniden oluşturur.
|
||||||
|
/// </summary>
|
||||||
|
public class WizardSeedFileDto
|
||||||
|
{
|
||||||
|
public ListFormWizardDto Wizard { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Tabloda IsDeleted alanı olup olmadığını belirtir (soft delete desteği).</summary>
|
||||||
|
public bool IsDeletedField { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Tabloda CreatorId alanı olup olmadığını belirtir (audit alanı).</summary>
|
||||||
|
public bool IsCreatedField { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bu Wizard çalışırken gerçekten YENİ oluşturulan kayıtlar.
|
||||||
|
/// Daha önce var olan kayıtlar buraya eklenmez, silme işleminde sadece bu liste kullanılır.
|
||||||
|
/// </summary>
|
||||||
|
public WizardInsertedRecordsDto InsertedRecords { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wizard Create sırasında gerçekten veritabanına eklenen kayıtların izleme listesi.
|
||||||
|
/// Silme işleminde sadece bu listeler kullanılır; paylaşılan kayıtlara dokunulmaz.
|
||||||
|
/// </summary>
|
||||||
|
public class WizardInsertedRecordsDto
|
||||||
|
{
|
||||||
|
/// <summary>Bu Wizard tarafından oluşturulan yeni LanguageKey değerleri (Key alanı).</summary>
|
||||||
|
public List<string> LanguageKeys { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>Bu Wizard tarafından oluşturulan yeni PermissionGroup isimleri.</summary>
|
||||||
|
public List<string> PermissionGroupNames { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>Bu Wizard tarafından oluşturulan yeni Permission isimleri.</summary>
|
||||||
|
public List<string> PermissionNames { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>Bu Wizard tarafından oluşturulan yeni Menu kodları.</summary>
|
||||||
|
public List<string> MenuCodes { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>Bu Wizard tarafından oluşturulan yeni DataSource kodları.</summary>
|
||||||
|
public List<string> DataSourceCodes { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Wizard seed dosyası özet bilgisi (yönetim listesi için).</summary>
|
||||||
|
public class WizardFileInfoDto
|
||||||
|
{
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public string WizardName { get; set; }
|
||||||
|
public string ListFormCode { get; set; }
|
||||||
|
public string CreatedAt { get; set; }
|
||||||
|
public bool HasInsertedRecords { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ using static Sozsoft.Platform.PlatformConsts;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms.ImportManager;
|
namespace Sozsoft.Platform.ListForms.ImportManager;
|
||||||
|
|
||||||
[Authorize()]
|
[Authorize]
|
||||||
public class ListFormImportAppService : PlatformAppService, IImportAppService
|
public class ListFormImportAppService : PlatformAppService, IImportAppService
|
||||||
{
|
{
|
||||||
private readonly IRepository<ListFormImport, Guid> _importSessionRepository;
|
private readonly IRepository<ListFormImport, Guid> _importSessionRepository;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -7,20 +8,20 @@ using Sozsoft.Languages.Entities;
|
||||||
using Sozsoft.Languages.Languages;
|
using Sozsoft.Languages.Languages;
|
||||||
using Sozsoft.Platform.Entities;
|
using Sozsoft.Platform.Entities;
|
||||||
using Sozsoft.Platform.Enums;
|
using Sozsoft.Platform.Enums;
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Identity;
|
|
||||||
using Volo.Abp.MultiTenancy;
|
using Volo.Abp.MultiTenancy;
|
||||||
using Volo.Abp.PermissionManagement;
|
using Volo.Abp.PermissionManagement;
|
||||||
using Volo.Abp.Uow;
|
using Volo.Abp.Uow;
|
||||||
using static Sozsoft.Platform.PlatformConsts;
|
using static Sozsoft.Platform.PlatformConsts;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Sozsoft.Languages;
|
using Sozsoft.Languages;
|
||||||
using Sozsoft.Platform.DynamicData;
|
using Sozsoft.Platform.DynamicData;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
public class ListFormWizardAppService(
|
public class ListFormWizardAppService(
|
||||||
IRepository<ListForm, Guid> repoListForm,
|
IRepository<ListForm, Guid> repoListForm,
|
||||||
IRepository<ListFormField, Guid> repoListFormField,
|
IRepository<ListFormField, Guid> repoListFormField,
|
||||||
|
|
@ -31,7 +32,7 @@ public class ListFormWizardAppService(
|
||||||
IRepository<PermissionGroupDefinitionRecord, Guid> repoPermGroup,
|
IRepository<PermissionGroupDefinitionRecord, Guid> repoPermGroup,
|
||||||
IRepository<Menu, Guid> repoMenu,
|
IRepository<Menu, Guid> repoMenu,
|
||||||
IPermissionGrantRepository permissionGrantRepository,
|
IPermissionGrantRepository permissionGrantRepository,
|
||||||
IConfiguration configuration,
|
IHostEnvironment hostEnvironment,
|
||||||
LanguageTextAppService languageTextAppService,
|
LanguageTextAppService languageTextAppService,
|
||||||
IDynamicDataManager dynamicDataManager
|
IDynamicDataManager dynamicDataManager
|
||||||
) : PlatformAppService(), IListFormWizardAppService
|
) : PlatformAppService(), IListFormWizardAppService
|
||||||
|
|
@ -45,7 +46,7 @@ public class ListFormWizardAppService(
|
||||||
private readonly IRepository<PermissionGroupDefinitionRecord, Guid> repoPermGroup = repoPermGroup;
|
private readonly IRepository<PermissionGroupDefinitionRecord, Guid> repoPermGroup = repoPermGroup;
|
||||||
private readonly IRepository<Menu, Guid> repoMenu = repoMenu;
|
private readonly IRepository<Menu, Guid> repoMenu = repoMenu;
|
||||||
private readonly IPermissionGrantRepository permissionGrantRepository = permissionGrantRepository;
|
private readonly IPermissionGrantRepository permissionGrantRepository = permissionGrantRepository;
|
||||||
private readonly IConfiguration _configuration = configuration;
|
private readonly IHostEnvironment _hostEnvironment = hostEnvironment;
|
||||||
private readonly LanguageTextAppService _languageTextAppService = languageTextAppService;
|
private readonly LanguageTextAppService _languageTextAppService = languageTextAppService;
|
||||||
private readonly IDynamicDataManager _dynamicDataManager = dynamicDataManager;
|
private readonly IDynamicDataManager _dynamicDataManager = dynamicDataManager;
|
||||||
private readonly string cultureNameDefault = PlatformConsts.DefaultLanguage;
|
private readonly string cultureNameDefault = PlatformConsts.DefaultLanguage;
|
||||||
|
|
@ -59,17 +60,21 @@ public class ListFormWizardAppService(
|
||||||
var descLangKey = WizardConsts.WizardKeyDesc(wizardName);
|
var descLangKey = WizardConsts.WizardKeyDesc(wizardName);
|
||||||
var code = WizardConsts.WizardKey(wizardName);
|
var code = WizardConsts.WizardKey(wizardName);
|
||||||
|
|
||||||
|
// Eklenen kayıtları takip et (silme işleminde kullanılır)
|
||||||
|
var inserted = new WizardInsertedRecordsDto();
|
||||||
|
|
||||||
//Dil - Language Keys
|
//Dil - Language Keys
|
||||||
await CreateLangKey(nameLangKey, input.LanguageTextMenuEn, input.LanguageTextMenuTr);
|
await CreateLangKey(nameLangKey, input.LanguageTextMenuEn, input.LanguageTextMenuTr, inserted);
|
||||||
await CreateLangKey(titleLangKey, input.LanguageTextTitleEn, input.LanguageTextTitleTr);
|
await CreateLangKey(titleLangKey, input.LanguageTextTitleEn, input.LanguageTextTitleTr, inserted);
|
||||||
await CreateLangKey(descLangKey, input.LanguageTextDescEn, input.LanguageTextDescTr);
|
await CreateLangKey(descLangKey, input.LanguageTextDescEn, input.LanguageTextDescTr, inserted);
|
||||||
|
|
||||||
//Permission Group
|
//Permission Group
|
||||||
var groupName = input.PermissionGroupName ?? PlatformConsts.AppName;
|
var groupName = input.PermissionGroupName ?? PlatformConsts.AppName;
|
||||||
if (!await repoPermGroup.AnyAsync(a => a.Name == groupName))
|
if (!await repoPermGroup.AnyAsync(a => a.Name == groupName))
|
||||||
{
|
{
|
||||||
await repoPermGroup.InsertAsync(new PermissionGroupDefinitionRecord(GuidGenerator.Create(), groupName, groupName), autoSave: false);
|
await repoPermGroup.InsertAsync(new PermissionGroupDefinitionRecord(GuidGenerator.Create(), groupName, groupName), autoSave: false);
|
||||||
await CreateLangKey(groupName, groupName, groupName);
|
await CreateLangKey(groupName, groupName, groupName, inserted);
|
||||||
|
inserted.PermissionGroupNames.Add(groupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permission'ları tek seferde kontrol et ve oluştur
|
// Permission'ları tek seferde kontrol et ve oluştur
|
||||||
|
|
@ -78,26 +83,54 @@ public class ListFormWizardAppService(
|
||||||
queryable.Where(a => a.GroupName == groupName)
|
queryable.Where(a => a.GroupName == groupName)
|
||||||
);
|
);
|
||||||
|
|
||||||
var permRead = existingPerms.FirstOrDefault(a => a.Name == code) ??
|
var permRead = existingPerms.FirstOrDefault(a => a.Name == code);
|
||||||
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, code, null, nameLangKey, true, MultiTenancySides.Both), autoSave: false);
|
if (permRead == null)
|
||||||
|
{
|
||||||
|
permRead = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, code, null, nameLangKey, true, MultiTenancySides.Both), autoSave: false);
|
||||||
|
inserted.PermissionNames.Add(permRead.Name);
|
||||||
|
}
|
||||||
|
|
||||||
var permCreate = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermCreate(wizardName)) ??
|
var permCreate = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermCreate(wizardName));
|
||||||
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermCreate(wizardName), permRead.Name, WizardConsts.LangKeyCreate, true, MultiTenancySides.Both), autoSave: false);
|
if (permCreate == null)
|
||||||
|
{
|
||||||
|
permCreate = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermCreate(wizardName), permRead.Name, WizardConsts.LangKeyCreate, true, MultiTenancySides.Both), autoSave: false);
|
||||||
|
inserted.PermissionNames.Add(permCreate.Name);
|
||||||
|
}
|
||||||
|
|
||||||
var permUpdate = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermUpdate(wizardName)) ??
|
var permUpdate = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermUpdate(wizardName));
|
||||||
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermUpdate(wizardName), permRead.Name, WizardConsts.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: false);
|
if (permUpdate == null)
|
||||||
|
{
|
||||||
|
permUpdate = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermUpdate(wizardName), permRead.Name, WizardConsts.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: false);
|
||||||
|
inserted.PermissionNames.Add(permUpdate.Name);
|
||||||
|
}
|
||||||
|
|
||||||
var permDelete = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermDelete(wizardName)) ??
|
var permDelete = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermDelete(wizardName));
|
||||||
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermDelete(wizardName), permRead.Name, WizardConsts.LangKeyDelete, true, MultiTenancySides.Both), autoSave: false);
|
if (permDelete == null)
|
||||||
|
{
|
||||||
|
permDelete = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermDelete(wizardName), permRead.Name, WizardConsts.LangKeyDelete, true, MultiTenancySides.Both), autoSave: false);
|
||||||
|
inserted.PermissionNames.Add(permDelete.Name);
|
||||||
|
}
|
||||||
|
|
||||||
var permExport = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermExport(wizardName)) ??
|
var permExport = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermExport(wizardName));
|
||||||
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermExport(wizardName), permRead.Name, WizardConsts.LangKeyExport, true, MultiTenancySides.Both), autoSave: false);
|
if (permExport == null)
|
||||||
|
{
|
||||||
|
permExport = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermExport(wizardName), permRead.Name, WizardConsts.LangKeyExport, true, MultiTenancySides.Both), autoSave: false);
|
||||||
|
inserted.PermissionNames.Add(permExport.Name);
|
||||||
|
}
|
||||||
|
|
||||||
var permImport = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermImport(wizardName)) ??
|
var permImport = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermImport(wizardName));
|
||||||
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermImport(wizardName), permRead.Name, WizardConsts.LangKeyImport, true, MultiTenancySides.Both), autoSave: false);
|
if (permImport == null)
|
||||||
|
{
|
||||||
|
permImport = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermImport(wizardName), permRead.Name, WizardConsts.LangKeyImport, true, MultiTenancySides.Both), autoSave: false);
|
||||||
|
inserted.PermissionNames.Add(permImport.Name);
|
||||||
|
}
|
||||||
|
|
||||||
var permNote = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermNote(wizardName)) ??
|
var permNote = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermNote(wizardName));
|
||||||
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermNote(wizardName), permRead.Name, WizardConsts.LangKeyNote, true, MultiTenancySides.Both), autoSave: false);
|
if (permNote == null)
|
||||||
|
{
|
||||||
|
permNote = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermNote(wizardName), permRead.Name, WizardConsts.LangKeyNote, true, MultiTenancySides.Both), autoSave: false);
|
||||||
|
inserted.PermissionNames.Add(permNote.Name);
|
||||||
|
}
|
||||||
|
|
||||||
// Permission Grants - Bulk Insert (only missing ones)
|
// Permission Grants - Bulk Insert (only missing ones)
|
||||||
var existingGrants = await permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName);
|
var existingGrants = await permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName);
|
||||||
|
|
@ -122,17 +155,23 @@ public class ListFormWizardAppService(
|
||||||
var menuParent = await AsyncExecuter.FirstOrDefaultAsync(menuQueryable.Where(a => a.Code == input.MenuParentCode));
|
var menuParent = await AsyncExecuter.FirstOrDefaultAsync(menuQueryable.Where(a => a.Code == input.MenuParentCode));
|
||||||
if (menuParent == null)
|
if (menuParent == null)
|
||||||
{
|
{
|
||||||
await CreateLangKey(WizardConsts.WizardKeyParent(wizardName), input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr);
|
var maxRootOrder = menuQueryable.Where(a => a.ParentCode == null || a.ParentCode == "").Select(a => (int?)a.Order).Max() ?? 0;
|
||||||
|
await CreateLangKey(WizardConsts.WizardKeyParent(wizardName), input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr, inserted);
|
||||||
menuParent = await repoMenu.InsertAsync(new Menu
|
menuParent = await repoMenu.InsertAsync(new Menu
|
||||||
{
|
{
|
||||||
Code = input.MenuParentCode,
|
Code = input.MenuParentCode,
|
||||||
DisplayName = WizardConsts.WizardKeyParent(wizardName),
|
DisplayName = WizardConsts.WizardKeyParent(wizardName),
|
||||||
IsDisabled = false,
|
IsDisabled = false,
|
||||||
|
Order = maxRootOrder + 1,
|
||||||
}, autoSave: false);
|
}, autoSave: false);
|
||||||
|
inserted.MenuCodes.Add(input.MenuParentCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Menu
|
//Menu
|
||||||
var menu = await AsyncExecuter.FirstOrDefaultAsync(menuQueryable.Where(a => a.Code == code)) ??
|
var maxChildOrder = menuQueryable.Where(a => a.ParentCode == menuParent.Code).Select(a => (int?)a.Order).Max() ?? 0;
|
||||||
|
var existingMenu = await AsyncExecuter.FirstOrDefaultAsync(menuQueryable.Where(a => a.Code == code));
|
||||||
|
if (existingMenu == null)
|
||||||
|
{
|
||||||
await repoMenu.InsertAsync(new Menu
|
await repoMenu.InsertAsync(new Menu
|
||||||
{
|
{
|
||||||
Code = code,
|
Code = code,
|
||||||
|
|
@ -144,8 +183,11 @@ public class ListFormWizardAppService(
|
||||||
ElementId = null,
|
ElementId = null,
|
||||||
CssClass = null,
|
CssClass = null,
|
||||||
Url = WizardConsts.MenuUrl(code),
|
Url = WizardConsts.MenuUrl(code),
|
||||||
RequiredPermissionName = permRead.Name
|
RequiredPermissionName = permRead.Name,
|
||||||
|
Order = maxChildOrder + 1,
|
||||||
}, autoSave: false);
|
}, autoSave: false);
|
||||||
|
inserted.MenuCodes.Add(code);
|
||||||
|
}
|
||||||
|
|
||||||
//DataSource kodu ile iligli kod blogu
|
//DataSource kodu ile iligli kod blogu
|
||||||
var dataSourceQueryable = await repoDataSource.GetQueryableAsync();
|
var dataSourceQueryable = await repoDataSource.GetQueryableAsync();
|
||||||
|
|
@ -158,6 +200,7 @@ public class ListFormWizardAppService(
|
||||||
DataSourceType = input.DataSourceConnectionString.IndexOf("Server") >= 0 ? DataSourceTypeEnum.Mssql : DataSourceTypeEnum.Postgresql,
|
DataSourceType = input.DataSourceConnectionString.IndexOf("Server") >= 0 ? DataSourceTypeEnum.Mssql : DataSourceTypeEnum.Postgresql,
|
||||||
ConnectionString = input.DataSourceConnectionString
|
ConnectionString = input.DataSourceConnectionString
|
||||||
}, autoSave: false);
|
}, autoSave: false);
|
||||||
|
inserted.DataSourceCodes.Add(input.DataSourceCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build EditingFormJson from wizard groups
|
// Build EditingFormJson from wizard groups
|
||||||
|
|
@ -265,13 +308,209 @@ public class ListFormWizardAppService(
|
||||||
LookupJson = item.LookupQuery.Length > 0 ? WizardConsts.DefaultLookupJson(item.LookupDataSourceType, item.DisplayExpr, item.ValueExpr, item.LookupQuery) : null,
|
LookupJson = item.LookupQuery.Length > 0 ? WizardConsts.DefaultLookupJson(item.LookupDataSourceType, item.DisplayExpr, item.ValueExpr, item.LookupQuery) : null,
|
||||||
}, autoSave: true);
|
}, autoSave: true);
|
||||||
|
|
||||||
await CreateLangKey(item.CaptionName, item.EnglishCaption, item.TurkishCaption);
|
await CreateLangKey(item.CaptionName, item.EnglishCaption, item.TurkishCaption, inserted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear Redis Cache
|
// Clear Redis Cache
|
||||||
await _languageTextAppService.ClearRedisCacheAsync();
|
await _languageTextAppService.ClearRedisCacheAsync();
|
||||||
|
|
||||||
|
// Wizard konfigürasyonunu seed dosyasına kaydet
|
||||||
|
await SaveWizardSeedFileAsync(input, isDeleted, isCreated, inserted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wizard konfigürasyonunu JSON dosyası olarak kaydeder.
|
||||||
|
/// Önce ContentRootPath'ten yukarı çıkarak Sozsoft.Platform.DbMigrator/Seeds/WizardData dizinini arar.
|
||||||
|
/// Bulamazsa ContentRootPath/Seeds/WizardData altına yazar.
|
||||||
|
/// Veritabanı silinip yeniden oluşturulduğunda WizardDataSeeder bu dosyaları okuyarak konfigürasyonu geri yükler.
|
||||||
|
/// </summary>
|
||||||
|
private async Task SaveWizardSeedFileAsync(ListFormWizardDto input, bool isDeletedField, bool isCreatedField, WizardInsertedRecordsDto inserted)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var outputPath = ResolveWizardSeedOutputPath();
|
||||||
|
Directory.CreateDirectory(outputPath);
|
||||||
|
|
||||||
|
var seedData = new WizardSeedFileDto
|
||||||
|
{
|
||||||
|
Wizard = input,
|
||||||
|
IsDeletedField = isDeletedField,
|
||||||
|
IsCreatedField = isCreatedField,
|
||||||
|
InsertedRecords = inserted
|
||||||
|
};
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(seedData, new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
var safeWizardName = string.Concat(input.WizardName.Trim().Split(Path.GetInvalidFileNameChars()));
|
||||||
|
var timestamp = DateTime.Now.ToString("yyyyMMddHHmm");
|
||||||
|
var filePath = Path.Combine(outputPath, $"{timestamp}_{safeWizardName}.json");
|
||||||
|
await File.WriteAllTextAsync(filePath, json);
|
||||||
|
|
||||||
|
Console.WriteLine($"[WizardSeed] Seed dosyası kaydedildi: {filePath}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Dosya kaydetme hatası wizard işlemini engellemez
|
||||||
|
Console.WriteLine($"[WizardSeed] Seed dosyası kaydedilemedi: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<WizardFileInfoDto>> GetFiles()
|
||||||
|
{
|
||||||
|
var outputPath = ResolveWizardSeedOutputPath();
|
||||||
|
var result = new List<WizardFileInfoDto>();
|
||||||
|
|
||||||
|
if (!Directory.Exists(outputPath))
|
||||||
|
return Task.FromResult(result);
|
||||||
|
|
||||||
|
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||||
|
|
||||||
|
foreach (var file in Directory.GetFiles(outputPath, "*.json").OrderBy(f => Path.GetFileName(f)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(file);
|
||||||
|
var seed = JsonSerializer.Deserialize<WizardSeedFileDto>(json, options);
|
||||||
|
var fileName = Path.GetFileName(file);
|
||||||
|
result.Add(new WizardFileInfoDto
|
||||||
|
{
|
||||||
|
FileName = fileName,
|
||||||
|
WizardName = seed?.Wizard?.WizardName ?? fileName,
|
||||||
|
ListFormCode = seed?.Wizard?.ListFormCode ?? string.Empty,
|
||||||
|
CreatedAt = fileName.Length >= 12 ? fileName[..12] : fileName,
|
||||||
|
HasInsertedRecords = seed?.InsertedRecords != null &&
|
||||||
|
(seed.InsertedRecords.LanguageKeys.Count > 0 ||
|
||||||
|
seed.InsertedRecords.PermissionGroupNames.Count > 0 ||
|
||||||
|
seed.InsertedRecords.PermissionNames.Count > 0 ||
|
||||||
|
seed.InsertedRecords.MenuCodes.Count > 0 ||
|
||||||
|
seed.InsertedRecords.DataSourceCodes.Count > 0)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
result.Add(new WizardFileInfoDto { FileName = Path.GetFileName(file) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteFile(string fileName)
|
||||||
|
{
|
||||||
|
// Güvenlik: sadece dosya adı, path traversal yasak
|
||||||
|
if (fileName.Contains('/') || fileName.Contains('\\') || fileName.Contains(".."))
|
||||||
|
throw new Volo.Abp.AbpException("Geçersiz dosya adı.");
|
||||||
|
|
||||||
|
var outputPath = ResolveWizardSeedOutputPath();
|
||||||
|
var filePath = Path.Combine(outputPath, fileName);
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
throw new Volo.Abp.AbpException($"Dosya bulunamadı: {fileName}");
|
||||||
|
|
||||||
|
var json = await File.ReadAllTextAsync(filePath);
|
||||||
|
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||||
|
var seed = JsonSerializer.Deserialize<WizardSeedFileDto>(json, options);
|
||||||
|
|
||||||
|
if (seed != null)
|
||||||
|
await DeleteWizardDataAsync(seed);
|
||||||
|
|
||||||
|
File.Delete(filePath);
|
||||||
|
|
||||||
|
await _languageTextAppService.ClearRedisCacheAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteWizardDataAsync(WizardSeedFileDto seed)
|
||||||
|
{
|
||||||
|
var ins = seed.InsertedRecords;
|
||||||
|
var listFormCode = seed.Wizard?.ListFormCode;
|
||||||
|
|
||||||
|
// ListForm ve alanları her zaman sil (wizard bunları her zaman oluşturur)
|
||||||
|
if (!string.IsNullOrWhiteSpace(listFormCode))
|
||||||
|
{
|
||||||
|
var lf = await repoListForm.FirstOrDefaultAsync(a => a.ListFormCode == listFormCode);
|
||||||
|
if (lf != null) await repoListForm.DeleteAsync(lf, autoSave: true);
|
||||||
|
|
||||||
|
var fields = await repoListFormField.GetListAsync(a => a.ListFormCode == listFormCode);
|
||||||
|
if (fields.Count > 0) await repoListFormField.DeleteManyAsync(fields, autoSave: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ins == null) return;
|
||||||
|
|
||||||
|
// Permission grants
|
||||||
|
if (ins.PermissionNames.Count > 0)
|
||||||
|
{
|
||||||
|
var grants = await permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName);
|
||||||
|
var toDelete = grants.Where(g => ins.PermissionNames.Contains(g.Name)).ToList();
|
||||||
|
foreach (var g in toDelete) await permissionGrantRepository.DeleteAsync(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permissions
|
||||||
|
foreach (var name in ins.PermissionNames)
|
||||||
|
{
|
||||||
|
var p = await repoPerm.FirstOrDefaultAsync(a => a.Name == name);
|
||||||
|
if (p != null) await repoPerm.DeleteAsync(p, autoSave: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission Groups
|
||||||
|
foreach (var name in ins.PermissionGroupNames)
|
||||||
|
{
|
||||||
|
var pg = await repoPermGroup.FirstOrDefaultAsync(a => a.Name == name);
|
||||||
|
if (pg != null) await repoPermGroup.DeleteAsync(pg, autoSave: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menus
|
||||||
|
foreach (var code in ins.MenuCodes)
|
||||||
|
{
|
||||||
|
var m = await repoMenu.FirstOrDefaultAsync(a => a.Code == code);
|
||||||
|
if (m != null) await repoMenu.DeleteAsync(m, autoSave: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataSources
|
||||||
|
foreach (var code in ins.DataSourceCodes)
|
||||||
|
{
|
||||||
|
var ds = await repoDataSource.FirstOrDefaultAsync(a => a.Code == code);
|
||||||
|
if (ds != null) await repoDataSource.DeleteAsync(ds, autoSave: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Language Keys ve Texts
|
||||||
|
var appName = PlatformConsts.AppName;
|
||||||
|
foreach (var key in ins.LanguageKeys)
|
||||||
|
{
|
||||||
|
var lk = await repoLangKey.FirstOrDefaultAsync(a => a.ResourceName == appName && a.Key == key);
|
||||||
|
if (lk == null) continue;
|
||||||
|
|
||||||
|
var texts = await repoLangText.GetListAsync(a => a.ResourceName == appName && a.Key == key);
|
||||||
|
if (texts.Count > 0) await repoLangText.DeleteManyAsync(texts, autoSave: true);
|
||||||
|
await repoLangKey.DeleteAsync(lk, autoSave: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DbMigrator projesinin Seeds/WizardData dizinini ContentRootPath'ten yukarı traversal ile bulur.
|
||||||
|
/// Tüm işletim sistemlerinde Path.Combine kullanır, separator karakteri içermez.
|
||||||
|
/// </summary>
|
||||||
|
private string ResolveWizardSeedOutputPath()
|
||||||
|
{
|
||||||
|
const string dbMigratorName = "Sozsoft.Platform.DbMigrator";
|
||||||
|
var dir = new DirectoryInfo(_hostEnvironment.ContentRootPath);
|
||||||
|
|
||||||
|
while (dir != null)
|
||||||
|
{
|
||||||
|
// src/Sozsoft.Platform.DbMigrator/Seeds altında ara
|
||||||
|
var candidate = Path.Combine(dir.FullName, "src", dbMigratorName, "Seeds");
|
||||||
|
if (Directory.Exists(candidate))
|
||||||
|
return Path.Combine(candidate, "WizardData");
|
||||||
|
|
||||||
|
// Sozsoft.Platform.DbMigrator/Seeds doğrudan altında ara
|
||||||
|
candidate = Path.Combine(dir.FullName, dbMigratorName, "Seeds");
|
||||||
|
if (Directory.Exists(candidate))
|
||||||
|
return Path.Combine(candidate, "WizardData");
|
||||||
|
|
||||||
|
dir = dir.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: çalışan API'nin yanında Seeds/WizardData
|
||||||
|
return Path.Combine(_hostEnvironment.ContentRootPath, "Seeds", "WizardData");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HashSet<string>> GetTableColumnNamesAsync(string dataSourceCode, SelectCommandTypeEnum commandType, string selectCommand)
|
private async Task<HashSet<string>> GetTableColumnNamesAsync(string dataSourceCode, SelectCommandTypeEnum commandType, string selectCommand)
|
||||||
|
|
@ -312,23 +551,27 @@ public class ListFormWizardAppService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<LanguageKey> CreateLangKey(string key, string textEn, string textTr)
|
private async Task<LanguageKey> CreateLangKey(string key, string textEn, string textTr, WizardInsertedRecordsDto inserted = null)
|
||||||
{
|
{
|
||||||
var res = PlatformConsts.AppName;
|
var res = PlatformConsts.AppName;
|
||||||
|
|
||||||
var langKey = await repoLangKey.FirstOrDefaultAsync(a => a.ResourceName == res && a.Key == key)
|
var existing = await repoLangKey.FirstOrDefaultAsync(a => a.ResourceName == res && a.Key == key);
|
||||||
?? await repoLangKey.InsertAsync(new LanguageKey { ResourceName = res, Key = key }, autoSave: true);
|
if (existing == null)
|
||||||
|
{
|
||||||
|
existing = await repoLangKey.InsertAsync(new LanguageKey { ResourceName = res, Key = key }, autoSave: true);
|
||||||
|
inserted?.LanguageKeys.Add(key);
|
||||||
|
}
|
||||||
|
|
||||||
var existingTexts = await repoLangText.GetListAsync(a => a.ResourceName == res && a.Key == langKey.Key);
|
var existingTexts = await repoLangText.GetListAsync(a => a.ResourceName == res && a.Key == existing.Key);
|
||||||
|
|
||||||
var existingEn = existingTexts.FirstOrDefault(a => a.CultureName == cultureNameDefault);
|
var existingEn = existingTexts.FirstOrDefault(a => a.CultureName == cultureNameDefault);
|
||||||
if (existingEn != null) await repoLangText.DeleteAsync(existingEn, autoSave: true);
|
if (existingEn != null) await repoLangText.DeleteAsync(existingEn, autoSave: true);
|
||||||
await repoLangText.InsertAsync(new LanguageText { ResourceName = res, Key = langKey.Key, CultureName = cultureNameDefault, Value = textEn }, autoSave: true);
|
await repoLangText.InsertAsync(new LanguageText { ResourceName = res, Key = existing.Key, CultureName = cultureNameDefault, Value = textEn }, autoSave: true);
|
||||||
|
|
||||||
var existingTr = existingTexts.FirstOrDefault(a => a.CultureName == LanguageCodes.Tr);
|
var existingTr = existingTexts.FirstOrDefault(a => a.CultureName == LanguageCodes.Tr);
|
||||||
if (existingTr != null) await repoLangText.DeleteAsync(existingTr, autoSave: true);
|
if (existingTr != null) await repoLangText.DeleteAsync(existingTr, autoSave: true);
|
||||||
await repoLangText.InsertAsync(new LanguageText { ResourceName = res, Key = langKey.Key, CultureName = LanguageCodes.Tr, Value = textTr }, autoSave: true);
|
await repoLangText.InsertAsync(new LanguageText { ResourceName = res, Key = existing.Key, CultureName = LanguageCodes.Tr, Value = textTr }, autoSave: true);
|
||||||
|
|
||||||
return langKey;
|
return existing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15998,6 +15998,12 @@
|
||||||
"en": "Listform Wizard",
|
"en": "Listform Wizard",
|
||||||
"tr": "Listform Sihirbazı"
|
"tr": "Listform Sihirbazı"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listforms.WizardManager",
|
||||||
|
"en": "Listform Wizard Manager",
|
||||||
|
"tr": "Listform Sihirbazı Yöneticisi"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "ListForms.Wizard.MenuInfo",
|
"key": "ListForms.Wizard.MenuInfo",
|
||||||
|
|
@ -16400,6 +16406,12 @@
|
||||||
"en": "Menu Information",
|
"en": "Menu Information",
|
||||||
"tr": "Menü Bilgileri"
|
"tr": "Menü Bilgileri"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.Wizard.Step4.MenuIcon",
|
||||||
|
"en": "Menu Icon",
|
||||||
|
"tr": "Menü Ikonu"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "ListForms.Wizard.Step4.ListFormSettings",
|
"key": "ListForms.Wizard.Step4.ListFormSettings",
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,14 @@
|
||||||
{
|
{
|
||||||
"key": "admin.listFormManagement.wizard",
|
"key": "admin.listFormManagement.wizard",
|
||||||
"path": "/admin/listform/wizard",
|
"path": "/admin/listform/wizard",
|
||||||
"componentPath": "@/views/admin/listForm/Wizard",
|
"componentPath": "@/views/admin/listForm/wizard/Wizard",
|
||||||
|
"routeType": "protected",
|
||||||
|
"authority": ["App.Listforms.Wizard"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "admin.listFormManagement.wizardManager",
|
||||||
|
"path": "/admin/listform/wizardManager",
|
||||||
|
"componentPath": "@/views/admin/listForm/wizard/WizardFileManager",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": ["App.Listforms.Wizard"]
|
"authority": ["App.Listforms.Wizard"]
|
||||||
},
|
},
|
||||||
|
|
@ -877,10 +884,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.DeveloperKit",
|
"ParentCode": "App.DeveloperKit",
|
||||||
"Code": "App.Listforms.Wizard",
|
"Code": "App.Listforms.WizardManager",
|
||||||
"DisplayName": "App.Listforms.Wizard",
|
"DisplayName": "App.Listforms.WizardManager",
|
||||||
"Order": 6,
|
"Order": 6,
|
||||||
"Url": "/admin/listform/wizard",
|
"Url": "/admin/listform/wizardManager",
|
||||||
"Icon": "FcFlashAuto",
|
"Icon": "FcFlashAuto",
|
||||||
"RequiredPermissionName": "App.Listforms.Wizard",
|
"RequiredPermissionName": "App.Listforms.Wizard",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
|
|
|
||||||
379
api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs
Normal file
379
api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs
Normal file
|
|
@ -0,0 +1,379 @@
|
||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Sozsoft.Languages.Entities;
|
||||||
|
using Sozsoft.Languages.Languages;
|
||||||
|
using Sozsoft.Platform.Entities;
|
||||||
|
using Sozsoft.Platform.Enums;
|
||||||
|
using Sozsoft.Platform.ListForms;
|
||||||
|
using Volo.Abp.Data;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
|
using Volo.Abp.PermissionManagement;
|
||||||
|
using static Sozsoft.Platform.PlatformConsts;
|
||||||
|
|
||||||
|
namespace Sozsoft.Platform.Data.Seeds;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wizard ile oluşturulan konfigürasyonları Seeds/WizardData/*.json dosyalarından okuyarak veritabanına aktarır.
|
||||||
|
/// Wizard çalıştıktan sonra oluşturulan JSON dosyaları projeye commit edildikten sonra,
|
||||||
|
/// veritabanı silinip yeniden oluşturulduğunda bu seeder tüm wizard konfigürasyonlarını geri yükler.
|
||||||
|
/// </summary>
|
||||||
|
public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
|
{
|
||||||
|
private readonly IRepository<LanguageKey, Guid> _repoLangKey;
|
||||||
|
private readonly IRepository<LanguageText, Guid> _repoLangText;
|
||||||
|
private readonly IRepository<PermissionGroupDefinitionRecord, Guid> _repoPermGroup;
|
||||||
|
private readonly IRepository<PermissionDefinitionRecord, Guid> _repoPerm;
|
||||||
|
private readonly IPermissionGrantRepository _permissionGrantRepository;
|
||||||
|
private readonly IRepository<Menu, Guid> _repoMenu;
|
||||||
|
private readonly IRepository<DataSource, Guid> _repoDataSource;
|
||||||
|
private readonly IRepository<ListForm, Guid> _repoListForm;
|
||||||
|
private readonly IRepository<ListFormField, Guid> _repoListFormField;
|
||||||
|
private readonly ILogger<WizardDataSeeder> _logger;
|
||||||
|
|
||||||
|
private readonly string _cultureNameDefault = PlatformConsts.DefaultLanguage;
|
||||||
|
private readonly string _appName = PlatformConsts.AppName;
|
||||||
|
|
||||||
|
public WizardDataSeeder(
|
||||||
|
IRepository<LanguageKey, Guid> repoLangKey,
|
||||||
|
IRepository<LanguageText, Guid> repoLangText,
|
||||||
|
IRepository<PermissionGroupDefinitionRecord, Guid> repoPermGroup,
|
||||||
|
IRepository<PermissionDefinitionRecord, Guid> repoPerm,
|
||||||
|
IPermissionGrantRepository permissionGrantRepository,
|
||||||
|
IRepository<Menu, Guid> repoMenu,
|
||||||
|
IRepository<DataSource, Guid> repoDataSource,
|
||||||
|
IRepository<ListForm, Guid> repoListForm,
|
||||||
|
IRepository<ListFormField, Guid> repoListFormField,
|
||||||
|
ILogger<WizardDataSeeder> logger)
|
||||||
|
{
|
||||||
|
_repoLangKey = repoLangKey;
|
||||||
|
_repoLangText = repoLangText;
|
||||||
|
_repoPermGroup = repoPermGroup;
|
||||||
|
_repoPerm = repoPerm;
|
||||||
|
_permissionGrantRepository = permissionGrantRepository;
|
||||||
|
_repoMenu = repoMenu;
|
||||||
|
_repoDataSource = repoDataSource;
|
||||||
|
_repoListForm = repoListForm;
|
||||||
|
_repoListFormField = repoListFormField;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SeedAsync(DataSeedContext context)
|
||||||
|
{
|
||||||
|
var wizardDataPath = Path.Combine(Directory.GetCurrentDirectory(), "Seeds", "WizardData");
|
||||||
|
if (!Directory.Exists(wizardDataPath))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Seeds/WizardData dizini bulunamadı, atlanıyor.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonFiles = Directory.GetFiles(wizardDataPath, "*.json").OrderBy(f => Path.GetFileName(f)).ToArray();
|
||||||
|
if (jsonFiles.Length == 0)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Seeds/WizardData dizininde JSON dosyası bulunamadı, atlanıyor.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||||
|
|
||||||
|
foreach (var filePath in jsonFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = await File.ReadAllTextAsync(filePath);
|
||||||
|
var seedFile = JsonSerializer.Deserialize<WizardSeedFileDto>(json, options);
|
||||||
|
|
||||||
|
if (seedFile?.Wizard == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"Geçersiz dosya atlandı: {filePath}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var wizardName = seedFile.Wizard.WizardName?.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(wizardName))
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"WizardName boş olduğu için atlandı: {filePath}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zaten seeded mi kontrol et (ListForm var mı?)
|
||||||
|
if (await _repoListForm.AnyAsync(a => a.ListFormCode == seedFile.Wizard.ListFormCode))
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"'{wizardName}' zaten mevcut, atlandı.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation($"'{wizardName}' uygulanıyor...");
|
||||||
|
await ApplyWizardSeedAsync(seedFile);
|
||||||
|
_logger.LogInformation($"'{wizardName}' başarıyla uygulandı.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Hata - {filePath}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplyWizardSeedAsync(WizardSeedFileDto seedFile)
|
||||||
|
{
|
||||||
|
var input = seedFile.Wizard;
|
||||||
|
var isDeleted = seedFile.IsDeletedField;
|
||||||
|
var isCreated = seedFile.IsCreatedField;
|
||||||
|
|
||||||
|
var wizardName = input.WizardName.Trim();
|
||||||
|
var titleLangKey = WizardConsts.WizardKeyTitle(wizardName);
|
||||||
|
var nameLangKey = WizardConsts.WizardKey(wizardName);
|
||||||
|
var descLangKey = WizardConsts.WizardKeyDesc(wizardName);
|
||||||
|
var code = WizardConsts.WizardKey(wizardName);
|
||||||
|
|
||||||
|
// Dil - Language Keys
|
||||||
|
await CreateLangKeyAsync(nameLangKey, input.LanguageTextMenuEn, input.LanguageTextMenuTr);
|
||||||
|
await CreateLangKeyAsync(titleLangKey, input.LanguageTextTitleEn, input.LanguageTextTitleTr);
|
||||||
|
await CreateLangKeyAsync(descLangKey, input.LanguageTextDescEn, input.LanguageTextDescTr);
|
||||||
|
|
||||||
|
// Permission Group
|
||||||
|
var groupName = input.PermissionGroupName ?? AppName;
|
||||||
|
if (!await _repoPermGroup.AnyAsync(a => a.Name == groupName))
|
||||||
|
{
|
||||||
|
await _repoPermGroup.InsertAsync(
|
||||||
|
new PermissionGroupDefinitionRecord(Guid.NewGuid(), groupName, groupName), autoSave: true);
|
||||||
|
await CreateLangKeyAsync(groupName, groupName, groupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permissions
|
||||||
|
var permQueryable = await _repoPerm.GetQueryableAsync();
|
||||||
|
var existingPerms = permQueryable.Where(a => a.GroupName == groupName).ToList();
|
||||||
|
|
||||||
|
var permRead = existingPerms.FirstOrDefault(a => a.Name == code) ??
|
||||||
|
await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||||||
|
Guid.NewGuid(), groupName, code, null, nameLangKey, true, MultiTenancySides.Both), autoSave: true);
|
||||||
|
|
||||||
|
var permCreate = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermCreate(wizardName)) ??
|
||||||
|
await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||||||
|
Guid.NewGuid(), groupName, WizardConsts.PermCreate(wizardName), permRead.Name, WizardConsts.LangKeyCreate, true, MultiTenancySides.Both), autoSave: true);
|
||||||
|
|
||||||
|
var permUpdate = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermUpdate(wizardName)) ??
|
||||||
|
await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||||||
|
Guid.NewGuid(), groupName, WizardConsts.PermUpdate(wizardName), permRead.Name, WizardConsts.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: true);
|
||||||
|
|
||||||
|
var permDelete = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermDelete(wizardName)) ??
|
||||||
|
await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||||||
|
Guid.NewGuid(), groupName, WizardConsts.PermDelete(wizardName), permRead.Name, WizardConsts.LangKeyDelete, true, MultiTenancySides.Both), autoSave: true);
|
||||||
|
|
||||||
|
var permExport = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermExport(wizardName)) ??
|
||||||
|
await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||||||
|
Guid.NewGuid(), groupName, WizardConsts.PermExport(wizardName), permRead.Name, WizardConsts.LangKeyExport, true, MultiTenancySides.Both), autoSave: true);
|
||||||
|
|
||||||
|
var permImport = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermImport(wizardName)) ??
|
||||||
|
await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||||||
|
Guid.NewGuid(), groupName, WizardConsts.PermImport(wizardName), permRead.Name, WizardConsts.LangKeyImport, true, MultiTenancySides.Both), autoSave: true);
|
||||||
|
|
||||||
|
var permNote = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermNote(wizardName)) ??
|
||||||
|
await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||||||
|
Guid.NewGuid(), groupName, WizardConsts.PermNote(wizardName), permRead.Name, WizardConsts.LangKeyNote, true, MultiTenancySides.Both), autoSave: true);
|
||||||
|
|
||||||
|
// Permission Grants - Admin role için
|
||||||
|
var existingGrants = await _permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName);
|
||||||
|
var existingGrantNames = existingGrants.Select(g => g.Name).ToHashSet();
|
||||||
|
|
||||||
|
var grantsToInsert = new[]
|
||||||
|
{
|
||||||
|
permRead.Name, permCreate.Name, permUpdate.Name,
|
||||||
|
permDelete.Name, permExport.Name, permImport.Name, permNote.Name
|
||||||
|
}
|
||||||
|
.Where(name => !existingGrantNames.Contains(name))
|
||||||
|
.Select(name => new PermissionGrant(Guid.NewGuid(), name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (grantsToInsert.Count > 0)
|
||||||
|
{
|
||||||
|
await _permissionGrantRepository.InsertManyAsync(grantsToInsert, autoSave: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menu Parent
|
||||||
|
var menuParent = await _repoMenu.FirstOrDefaultAsync(a => a.Code == input.MenuParentCode);
|
||||||
|
if (menuParent == null)
|
||||||
|
{
|
||||||
|
await CreateLangKeyAsync(WizardConsts.WizardKeyParent(wizardName), input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr);
|
||||||
|
menuParent = await _repoMenu.InsertAsync(new Menu
|
||||||
|
{
|
||||||
|
Code = input.MenuParentCode,
|
||||||
|
DisplayName = WizardConsts.WizardKeyParent(wizardName),
|
||||||
|
IsDisabled = false,
|
||||||
|
}, autoSave: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menu
|
||||||
|
if (!await _repoMenu.AnyAsync(a => a.Code == code))
|
||||||
|
{
|
||||||
|
await _repoMenu.InsertAsync(new Menu
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
DisplayName = nameLangKey,
|
||||||
|
IsDisabled = false,
|
||||||
|
ParentCode = menuParent.Code,
|
||||||
|
Icon = input.MenuIcon ?? WizardConsts.MenuIcon,
|
||||||
|
Target = null,
|
||||||
|
ElementId = null,
|
||||||
|
CssClass = null,
|
||||||
|
Url = WizardConsts.MenuUrl(code),
|
||||||
|
RequiredPermissionName = permRead.Name
|
||||||
|
}, autoSave: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataSource
|
||||||
|
if (!await _repoDataSource.AnyAsync(a => a.Code == input.DataSourceCode))
|
||||||
|
{
|
||||||
|
await _repoDataSource.InsertAsync(new DataSource
|
||||||
|
{
|
||||||
|
Code = input.DataSourceCode,
|
||||||
|
DataSourceType = input.DataSourceConnectionString != null &&
|
||||||
|
input.DataSourceConnectionString.IndexOf("Server", StringComparison.OrdinalIgnoreCase) >= 0
|
||||||
|
? DataSourceTypeEnum.Mssql
|
||||||
|
: DataSourceTypeEnum.Postgresql,
|
||||||
|
ConnectionString = input.DataSourceConnectionString
|
||||||
|
}, autoSave: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditingFormJson
|
||||||
|
var editingFormDtos = input.Groups
|
||||||
|
.Select((g, gi) => new EditingFormDto
|
||||||
|
{
|
||||||
|
Order = gi + 1,
|
||||||
|
Caption = g.Caption,
|
||||||
|
ColCount = g.ColCount,
|
||||||
|
ColSpan = g.ColCount,
|
||||||
|
ItemType = "group",
|
||||||
|
Items = g.Items
|
||||||
|
.Where(i => i.DataField != input.KeyFieldName)
|
||||||
|
.Select((it, ii) => new EditingFormItemDto
|
||||||
|
{
|
||||||
|
Order = ii + 1,
|
||||||
|
DataField = it.DataField,
|
||||||
|
EditorType2 = it.EditorType,
|
||||||
|
ColSpan = it.ColSpan,
|
||||||
|
EditorOptions = string.IsNullOrWhiteSpace(it.EditorOptions) ? null : it.EditorOptions,
|
||||||
|
EditorScript = string.IsNullOrWhiteSpace(it.EditorScript) ? null : it.EditorScript,
|
||||||
|
IsRequired = it.IsRequired,
|
||||||
|
})
|
||||||
|
.ToArray()
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// ListForm
|
||||||
|
await _repoListForm.InsertAsync(new ListForm
|
||||||
|
{
|
||||||
|
ListFormType = ListFormTypeEnum.List,
|
||||||
|
PageSize = 10,
|
||||||
|
ExportJson = WizardConsts.DefaultExportJson,
|
||||||
|
IsSubForm = false,
|
||||||
|
ShowNote = true,
|
||||||
|
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler),
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
ListFormCode = input.ListFormCode,
|
||||||
|
Name = nameLangKey,
|
||||||
|
Title = titleLangKey,
|
||||||
|
Description = descLangKey,
|
||||||
|
DataSourceCode = input.DataSourceCode,
|
||||||
|
IsTenant = input.IsTenant,
|
||||||
|
IsBranch = input.IsBranch,
|
||||||
|
IsOrganizationUnit = input.IsOrganizationUnit,
|
||||||
|
SelectCommandType = input.SelectCommandType,
|
||||||
|
SelectCommand = input.SelectCommand,
|
||||||
|
KeyFieldName = input.KeyFieldName,
|
||||||
|
KeyFieldDbSourceType = input.KeyFieldDbSourceType,
|
||||||
|
DefaultFilter = isDeleted ? WizardConsts.DefaultFilterJson : null,
|
||||||
|
SortMode = GridOptions.SortModeSingle,
|
||||||
|
FilterRowJson = WizardConsts.DefaultFilterRowJson,
|
||||||
|
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
|
||||||
|
SearchPanelJson = WizardConsts.DefaultSearchPanelJson,
|
||||||
|
GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }),
|
||||||
|
SelectionJson = WizardConsts.DefaultSelectionSingleJson,
|
||||||
|
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
|
||||||
|
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
||||||
|
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
|
||||||
|
DeleteFieldsDefaultValueJson = isDeleted
|
||||||
|
? WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType)
|
||||||
|
: WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType),
|
||||||
|
InsertFieldsDefaultValueJson = isCreated
|
||||||
|
? WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType)
|
||||||
|
: WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType),
|
||||||
|
PagerOptionJson = WizardConsts.DefaultPagerOptionJson,
|
||||||
|
EditingOptionJson = WizardConsts.DefaultEditingOptionJson(titleLangKey, 600, 500, input.AllowDeleting, input.AllowAdding, input.AllowUpdating, input.ConfirmDelete, false, input.AllowDetail),
|
||||||
|
EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null,
|
||||||
|
}, autoSave: true);
|
||||||
|
|
||||||
|
// ListFormFields
|
||||||
|
var fieldOrder = 0;
|
||||||
|
foreach (var group in input.Groups)
|
||||||
|
{
|
||||||
|
foreach (var item in group.Items)
|
||||||
|
{
|
||||||
|
fieldOrder++;
|
||||||
|
await _repoListFormField.InsertAsync(new ListFormField
|
||||||
|
{
|
||||||
|
ListFormCode = input.ListFormCode,
|
||||||
|
FieldName = item.DataField,
|
||||||
|
CaptionName = item.CaptionName,
|
||||||
|
Visible = item.DataField != input.KeyFieldName,
|
||||||
|
IsActive = true,
|
||||||
|
AllowSearch = true,
|
||||||
|
ListOrderNo = fieldOrder,
|
||||||
|
SourceDbType = item.DbSourceType,
|
||||||
|
CultureName = PlatformConsts.DefaultLanguage,
|
||||||
|
PermissionJson = WizardConsts.DefaultFieldPermissionJson(code),
|
||||||
|
ColumnCustomizationJson = WizardConsts.DefaultColumnCustomizationJson,
|
||||||
|
ColumnFilterJson = WizardConsts.DefaultColumnFilteringJson,
|
||||||
|
PivotSettingsJson = WizardConsts.DefaultPivotSettingsJson,
|
||||||
|
LookupJson = !string.IsNullOrWhiteSpace(item.LookupQuery)
|
||||||
|
? WizardConsts.DefaultLookupJson(item.LookupDataSourceType, item.DisplayExpr, item.ValueExpr, item.LookupQuery)
|
||||||
|
: null,
|
||||||
|
}, autoSave: true);
|
||||||
|
|
||||||
|
await CreateLangKeyAsync(item.CaptionName, item.EnglishCaption, item.TurkishCaption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CreateLangKeyAsync(string key, string textEn, string textTr)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(key)) return;
|
||||||
|
|
||||||
|
var langKey = await _repoLangKey.FirstOrDefaultAsync(a => a.ResourceName == _appName && a.Key == key)
|
||||||
|
?? await _repoLangKey.InsertAsync(new LanguageKey { ResourceName = _appName, Key = key }, autoSave: true);
|
||||||
|
|
||||||
|
var existingTexts = await _repoLangText.GetListAsync(a => a.ResourceName == _appName && a.Key == langKey.Key);
|
||||||
|
|
||||||
|
var existingEn = existingTexts.FirstOrDefault(a => a.CultureName == _cultureNameDefault);
|
||||||
|
if (existingEn == null)
|
||||||
|
{
|
||||||
|
await _repoLangText.InsertAsync(new LanguageText
|
||||||
|
{
|
||||||
|
ResourceName = _appName,
|
||||||
|
Key = langKey.Key,
|
||||||
|
CultureName = _cultureNameDefault,
|
||||||
|
Value = textEn ?? key
|
||||||
|
}, autoSave: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingTr = existingTexts.FirstOrDefault(a => a.CultureName == LanguageCodes.Tr);
|
||||||
|
if (existingTr == null)
|
||||||
|
{
|
||||||
|
await _repoLangText.InsertAsync(new LanguageText
|
||||||
|
{
|
||||||
|
ResourceName = _appName,
|
||||||
|
Key = langKey.Key,
|
||||||
|
CultureName = LanguageCodes.Tr,
|
||||||
|
Value = textTr ?? key
|
||||||
|
}, autoSave: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -77,6 +77,10 @@
|
||||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="Seeds\WizardData\*.json">
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,14 @@ export interface ListFormWizardDto {
|
||||||
groups?: ListFormWizardColumnGroupDto[]
|
groups?: ListFormWizardColumnGroupDto[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WizardFileInfoDto {
|
||||||
|
fileName: string
|
||||||
|
wizardName: string
|
||||||
|
listFormCode: string
|
||||||
|
createdAt: string
|
||||||
|
hasInsertedRecords: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface ListFormJsonRowDto {
|
export interface ListFormJsonRowDto {
|
||||||
id?: string
|
id?: string
|
||||||
index: number
|
index: number
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,7 @@
|
||||||
import { ListFormJsonRowDto, ListFormWizardDto } from '../../proxy/admin/list-form/models'
|
import { ListFormJsonRowDto } from '../../proxy/admin/list-form/models'
|
||||||
import { FieldsDefaultValueDto, GridOptionsEditDto } from '../../proxy/form/models'
|
import { FieldsDefaultValueDto, GridOptionsEditDto } from '../../proxy/form/models'
|
||||||
import apiService from '../api.service'
|
import apiService from '../api.service'
|
||||||
|
|
||||||
export const postListFormWizard = (input: ListFormWizardDto) =>
|
|
||||||
apiService.fetchData({
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/app/list-form-wizard`,
|
|
||||||
data: input,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getListFormByCode = (listFormCode: string) =>
|
export const getListFormByCode = (listFormCode: string) =>
|
||||||
apiService.fetchData<GridOptionsEditDto>({
|
apiService.fetchData<GridOptionsEditDto>({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
|
||||||
22
ui/src/services/wizard.service.ts
Normal file
22
ui/src/services/wizard.service.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { ListFormWizardDto, WizardFileInfoDto } from '../proxy/admin/list-form/models'
|
||||||
|
import apiService from './api.service'
|
||||||
|
|
||||||
|
export const postListFormWizard = (input: ListFormWizardDto) =>
|
||||||
|
apiService.fetchData({
|
||||||
|
method: 'POST',
|
||||||
|
url: `/api/app/list-form-wizard`,
|
||||||
|
data: input,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getWizardFiles = () =>
|
||||||
|
apiService.fetchData<WizardFileInfoDto[]>({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/api/app/list-form-wizard/files`,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const deleteWizardFile = (fileName: string) =>
|
||||||
|
apiService.fetchData({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `/api/app/list-form-wizard/file`,
|
||||||
|
params: { fileName },
|
||||||
|
})
|
||||||
|
|
@ -11,7 +11,6 @@ import * as Yup from 'yup'
|
||||||
import { getMenus } from '@/services/menu.service'
|
import { getMenus } from '@/services/menu.service'
|
||||||
import { getPermissions } from '@/services/identity.service'
|
import { getPermissions } from '@/services/identity.service'
|
||||||
import { DbTypeEnum, SelectCommandTypeEnum } from '@/proxy/form/models'
|
import { DbTypeEnum, SelectCommandTypeEnum } from '@/proxy/form/models'
|
||||||
import { postListFormWizard } from '@/services/admin/list-form.service'
|
|
||||||
import { getDataSources } from '@/services/data-source.service'
|
import { getDataSources } from '@/services/data-source.service'
|
||||||
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
||||||
import type { SqlObjectExplorerDto, DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
import type { SqlObjectExplorerDto, DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
|
|
@ -27,8 +26,9 @@ import WizardStep2 from './WizardStep2'
|
||||||
import WizardStep3, { WizardGroup } from './WizardStep3'
|
import WizardStep3, { WizardGroup } from './WizardStep3'
|
||||||
import WizardStep4 from './WizardStep4'
|
import WizardStep4 from './WizardStep4'
|
||||||
import { Container } from '@/components/shared'
|
import { Container } from '@/components/shared'
|
||||||
import { sqlDataTypeToDbType } from './edit/options'
|
import { sqlDataTypeToDbType } from '../edit/options'
|
||||||
import { useStoreActions } from '@/store/store'
|
import { useStoreActions } from '@/store/store'
|
||||||
|
import { postListFormWizard } from '@/services/wizard.service'
|
||||||
|
|
||||||
// ─── Formik initial values & validation ──────────────────────────────────────
|
// ─── Formik initial values & validation ──────────────────────────────────────
|
||||||
const initialValues: ListFormWizardDto = {
|
const initialValues: ListFormWizardDto = {
|
||||||
217
ui/src/views/admin/listForm/wizard/WizardFileManager.tsx
Normal file
217
ui/src/views/admin/listForm/wizard/WizardFileManager.tsx
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { Button, Notification, toast } from '@/components/ui'
|
||||||
|
import Container from '@/components/shared/Container'
|
||||||
|
import { WizardFileInfoDto } from '@/proxy/admin/list-form/models'
|
||||||
|
import { FaTrash, FaSync, FaDatabase, FaPlus, FaExclamationTriangle } from 'react-icons/fa'
|
||||||
|
import { deleteWizardFile, getWizardFiles } from '@/services/wizard.service'
|
||||||
|
import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
import { useStoreState } from '@/store/store'
|
||||||
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
|
|
||||||
|
// Timestamp formatı: "202605021730" → "2026-05-02 17:30"
|
||||||
|
const formatTimestamp = (raw: string): string => {
|
||||||
|
if (raw.length < 12) return raw
|
||||||
|
const y = raw.slice(0, 4)
|
||||||
|
const mo = raw.slice(4, 6)
|
||||||
|
const d = raw.slice(6, 8)
|
||||||
|
const h = raw.slice(8, 10)
|
||||||
|
const mi = raw.slice(10, 12)
|
||||||
|
return `${y}-${mo}-${d} ${h}:${mi}`
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConfirmState {
|
||||||
|
fileName: string
|
||||||
|
wizardName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const WizardFileManager = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
const mode = useStoreState((state) => state.theme.mode)
|
||||||
|
const MenuIcon = useCurrentMenuIcon('w-5 h-5')
|
||||||
|
|
||||||
|
const [files, setFiles] = useState<WizardFileInfoDto[]>([])
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [deletingFile, setDeletingFile] = useState<string | null>(null)
|
||||||
|
const [confirm, setConfirm] = useState<ConfirmState | null>(null)
|
||||||
|
|
||||||
|
const loadFiles = useCallback(async () => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const res = await getWizardFiles()
|
||||||
|
setFiles(res.data ?? [])
|
||||||
|
} catch {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="danger">Wizard dosyaları yüklenemedi.</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadFiles()
|
||||||
|
}, [loadFiles])
|
||||||
|
|
||||||
|
const handleDeleteConfirm = async () => {
|
||||||
|
if (!confirm) return
|
||||||
|
setDeletingFile(confirm.fileName)
|
||||||
|
setConfirm(null)
|
||||||
|
try {
|
||||||
|
await deleteWizardFile(confirm.fileName)
|
||||||
|
toast.push(
|
||||||
|
<Notification type="success" duration={3000}>
|
||||||
|
<strong>{confirm.wizardName}</strong> silindi.
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
await loadFiles()
|
||||||
|
} catch (err: any) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="danger">
|
||||||
|
Silme başarısız: {err?.message ?? 'Bilinmeyen hata'}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
setDeletingFile(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
{/* ── Header ─────────────────────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'flex items-center gap-2 pb-1 border-b',
|
||||||
|
mode === 'light' ? 'border-gray-200' : 'border-neutral-700',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{MenuIcon}
|
||||||
|
<h4 className="text-sm font-medium">
|
||||||
|
{translate('::App.Listforms.WizardManager') || 'Wizard Seed Dosyaları'}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div className="flex gap-1 ml-auto">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="default"
|
||||||
|
title={translate('::App.Platform.Refresh') || 'Yenile'}
|
||||||
|
loading={loading}
|
||||||
|
onClick={loadFiles}
|
||||||
|
>
|
||||||
|
<FaSync />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="solid"
|
||||||
|
onClick={() => navigate(ROUTES_ENUM.protected.saas.listFormManagement.wizard)}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
<FaPlus className="mr-1" />
|
||||||
|
{translate('::ListForms.ListForm.AddNewRecord') || 'Add New Record'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
|
||||||
|
{files.length === 0 && !loading && (
|
||||||
|
<p className="text-xs text-gray-400 text-center py-4">
|
||||||
|
Henüz kaydedilmiş wizard dosyası yok.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<p className="text-xs text-gray-400 text-center py-4 animate-pulse">Yükleniyor...</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
{files.map((f) => (
|
||||||
|
<div
|
||||||
|
key={f.fileName}
|
||||||
|
className="flex items-center justify-between p-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
|
<FaDatabase className="text-indigo-400 shrink-0" />
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="font-medium text-sm text-gray-800 dark:text-gray-200 truncate">
|
||||||
|
{f.wizardName || f.fileName}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400 flex gap-3 mt-0.5">
|
||||||
|
<span>{formatTimestamp(f.createdAt)}</span>
|
||||||
|
<span className="truncate">{f.listFormCode}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 shrink-0 ml-3">
|
||||||
|
{!f.hasInsertedRecords && (
|
||||||
|
<span
|
||||||
|
title="Bu dosyada izlenen kayıt bilgisi yok. Eski format olabilir."
|
||||||
|
className="text-yellow-500 text-xs flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<FaExclamationTriangle />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="plain"
|
||||||
|
className="text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||||
|
type="button"
|
||||||
|
loading={deletingFile === f.fileName}
|
||||||
|
onClick={() => setConfirm({ fileName: f.fileName, wizardName: f.wizardName || f.fileName })}
|
||||||
|
>
|
||||||
|
<FaTrash />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Confirm Dialog */}
|
||||||
|
{confirm && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
|
||||||
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-xl p-6 max-w-sm w-full mx-4">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<FaExclamationTriangle className="text-red-500 text-xl shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-gray-800 dark:text-gray-200">Wizard Sil</p>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">
|
||||||
|
<strong>{confirm.wizardName}</strong> wizard'ı ve buna ait tüm veritabanı
|
||||||
|
kayıtları (izin, menü, dil, listform) silinecek. Bu işlem geri alınamaz.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-2 mt-4">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="plain"
|
||||||
|
type="button"
|
||||||
|
onClick={() => setConfirm(null)}
|
||||||
|
>
|
||||||
|
İptal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="solid"
|
||||||
|
color="red"
|
||||||
|
type="button"
|
||||||
|
onClick={handleDeleteConfirm}
|
||||||
|
>
|
||||||
|
Evet, Sil
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WizardFileManager
|
||||||
|
|
@ -3,15 +3,10 @@ import { ListFormWizardDto } from '@/proxy/admin/list-form/models'
|
||||||
import { SelectCommandTypeEnum } from '@/proxy/form/models'
|
import { SelectCommandTypeEnum } from '@/proxy/form/models'
|
||||||
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
|
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
|
||||||
import { SelectBoxOption } from '@/types/shared'
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
import {
|
|
||||||
dbSourceTypeOptions,
|
|
||||||
listFormDefaultLayoutOptions,
|
|
||||||
selectCommandTypeOptions,
|
|
||||||
sqlDataTypeToDbType,
|
|
||||||
} from './edit/options'
|
|
||||||
import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik'
|
import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik'
|
||||||
import CreatableSelect from 'react-select/creatable'
|
import CreatableSelect from 'react-select/creatable'
|
||||||
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
|
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
|
||||||
|
import { dbSourceTypeOptions, listFormDefaultLayoutOptions, selectCommandTypeOptions, sqlDataTypeToDbType } from '../edit/options'
|
||||||
|
|
||||||
// ─── Props ────────────────────────────────────────────────────────────────────
|
// ─── Props ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { Button } from '@/components/ui'
|
import { Button } from '@/components/ui'
|
||||||
import type { ListFormWizardDto } from '@/proxy/admin/list-form/models'
|
import type { ListFormWizardDto } from '@/proxy/admin/list-form/models'
|
||||||
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
import type { WizardGroup } from './WizardStep3'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
FaArrowLeft,
|
FaArrowLeft,
|
||||||
|
|
@ -13,7 +12,8 @@ import {
|
||||||
FaRocket,
|
FaRocket,
|
||||||
FaSpinner,
|
FaSpinner,
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
import { dbSourceTypeOptions, selectCommandTypeOptions } from './edit/options'
|
import { WizardGroup } from './WizardStep3'
|
||||||
|
import { dbSourceTypeOptions, selectCommandTypeOptions } from '../edit/options'
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ import {
|
||||||
MenuTreeNode,
|
MenuTreeNode,
|
||||||
buildMenuTree,
|
buildMenuTree,
|
||||||
filterNonLinkNodes,
|
filterNonLinkNodes,
|
||||||
} from '@/views/admin/listForm/WizardStep1'
|
} from '@/views/admin/listForm/wizard/WizardStep1'
|
||||||
import { MenuAddDialog } from '../shared/MenuAddDialog'
|
import { MenuAddDialog } from '../shared/MenuAddDialog'
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue