Seeder ve WizardFileManager düzeltmesi

This commit is contained in:
Sedat Öztürk 2026-05-02 22:31:43 +03:00
parent d44555ad6a
commit 444261ba39
3 changed files with 147 additions and 108 deletions

View file

@ -15998,6 +15998,36 @@
"en": "Listform Wizard", "en": "Listform Wizard",
"tr": "Listform Sihirbazı" "tr": "Listform Sihirbazı"
}, },
{
"resourceName": "Platform",
"key": "App.Listforms.WizardFileLoadError",
"en": "Failed to load wizard files.",
"tr": "Wizard dosyaları yüklenemedi."
},
{
"resourceName": "Platform",
"key": "App.Listforms.WizardNoFiles",
"en": "No wizard file saved yet.",
"tr": "Henüz kaydedilmiş wizard dosyası yok."
},
{
"resourceName": "Platform",
"key": "App.Listforms.WizardFileDeleteConfirm",
"en": "Are you sure you want to delete this wizard file? All related database records (permission, menu, language, listform) will also be deleted. This action cannot be undone.",
"tr": "Bu wizard dosyasını silmek istediğinizden emin misiniz? ve buna ait tüm veritabanı kayıtları (izin, menü, dil, listform) silinecek. Bu işlem geri alınamaz."
},
{
"resourceName": "Platform",
"key": "App.Listforms.WizardFileDeleteError",
"en": "Failed to delete wizard file.",
"tr": "Wizard dosyası silinemedi."
},
{
"resourceName": "Platform",
"key": "App.Listforms.WizardFileDeleteSuccess",
"en": "Wizard file deleted successfully.",
"tr": "Wizard dosyası başarıyla silindi."
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.Listforms.WizardManager", "key": "App.Listforms.WizardManager",

View file

@ -70,18 +70,18 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
var wizardDataPath = Path.Combine(Directory.GetCurrentDirectory(), "Seeds", "WizardData"); var wizardDataPath = Path.Combine(Directory.GetCurrentDirectory(), "Seeds", "WizardData");
if (!Directory.Exists(wizardDataPath)) if (!Directory.Exists(wizardDataPath))
{ {
_logger.LogInformation("Seeds/WizardData dizini bulunamadı, atlanıyor."); _logger.LogInformation("Seeds/WizardData directory not found, skipping.");
return; return;
} }
var jsonFiles = Directory.GetFiles(wizardDataPath, "*.json").OrderBy(f => Path.GetFileName(f)).ToArray(); var jsonFiles = Directory.GetFiles(wizardDataPath, "*.json").OrderBy(f => Path.GetFileName(f)).ToArray();
if (jsonFiles.Length == 0) if (jsonFiles.Length == 0)
{ {
_logger.LogInformation("Seeds/WizardData dizininde JSON dosyası bulunamadı, atlanıyor."); _logger.LogInformation("No JSON files found in Seeds/WizardData directory, skipping.");
return; return;
} }
_logger.LogInformation("WizardDataSeeder başladı. {Count} dosya işlenecek.", jsonFiles.Length); _logger.LogInformation("WizardDataSeeder started. {Count} files to be processed.", jsonFiles.Length);
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
@ -94,14 +94,14 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
if (seedFile?.Wizard == null) if (seedFile?.Wizard == null)
{ {
_logger.LogWarning("Geçersiz dosya atlandı: {FilePath}", filePath); _logger.LogWarning("Invalid file skipped: {FilePath}", filePath);
continue; continue;
} }
var wizardName = seedFile.Wizard.WizardName?.Trim(); var wizardName = seedFile.Wizard.WizardName?.Trim();
if (string.IsNullOrWhiteSpace(wizardName)) if (string.IsNullOrWhiteSpace(wizardName))
{ {
_logger.LogWarning("WizardName boş olduğu için atlandı: {FilePath}", filePath); _logger.LogWarning("WizardName is empty, skipped: {FilePath}", filePath);
continue; continue;
} }
@ -110,13 +110,13 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
// Zaten seeded mi kontrol et (ListForm var mı?) // Zaten seeded mi kontrol et (ListForm var mı?)
if (await _repoListForm.AnyAsync(a => a.ListFormCode == seedFile.Wizard.ListFormCode)) if (await _repoListForm.AnyAsync(a => a.ListFormCode == seedFile.Wizard.ListFormCode))
{ {
_logger.LogInformation("[{File}] '{WizardName}' zaten mevcut, atlandı.", fileName, wizardName); _logger.LogInformation("[{File}] '{WizardName}' already exists, skipped.", fileName, wizardName);
continue; continue;
} }
_logger.LogInformation("[{File}] '{WizardName}' uygulanıyor...", fileName, wizardName); _logger.LogInformation("[{File}] '{WizardName}' is being applied...", fileName, wizardName);
await ApplyWizardSeedAsync(seedFile); await ApplyWizardSeedAsync(seedFile);
_logger.LogInformation("[{File}] '{WizardName}' başarıyla uygulandı.", fileName, wizardName); _logger.LogInformation("[{File}] '{WizardName}' has been successfully applied.", fileName, wizardName);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -190,23 +190,23 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
permNote = await _repoPerm.InsertAsync(new PermissionDefinitionRecord( permNote = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
Guid.NewGuid(), groupName, WizardConsts.PermNote(wizardName), permRead.Name, WizardConsts.LangKeyNote, true, MultiTenancySides.Both), autoSave: true); Guid.NewGuid(), groupName, WizardConsts.PermNote(wizardName), permRead.Name, WizardConsts.LangKeyNote, true, MultiTenancySides.Both), autoSave: true);
// Permission Grants - Admin role için, sadece eksik olanları ekle // // Permission Grants - Admin role için, sadece eksik olanları ekle
var existingGrants = await _permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName); // var existingGrants = await _permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName);
var existingGrantNames = new HashSet<string>(existingGrants.Select(g => g.Name)); // var existingGrantNames = new HashSet<string>(existingGrants.Select(g => g.Name));
var grantsToInsert = new[] // var grantsToInsert = new[]
{ // {
permRead.Name, permCreate.Name, permUpdate.Name, // permRead.Name, permCreate.Name, permUpdate.Name,
permDelete.Name, permExport.Name, permImport.Name, permNote.Name // permDelete.Name, permExport.Name, permImport.Name, permNote.Name
} // }
.Where(name => !existingGrantNames.Contains(name)) // .Where(name => !existingGrantNames.Contains(name))
.Select(name => new PermissionGrant(Guid.NewGuid(), name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName)) // .Select(name => new PermissionGrant(Guid.NewGuid(), name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName))
.ToList(); // .ToList();
if (grantsToInsert.Count > 0) // if (grantsToInsert.Count > 0)
{ // {
await _permissionGrantRepository.InsertManyAsync(grantsToInsert, autoSave: true); // await _permissionGrantRepository.InsertManyAsync(grantsToInsert, autoSave: true);
} // }
// Menu Parent // Menu Parent
var menuQueryable = await _repoMenu.GetQueryableAsync(); var menuQueryable = await _repoMenu.GetQueryableAsync();

View file

@ -4,7 +4,14 @@ import classNames from 'classnames'
import { Button, Input, Notification, toast } from '@/components/ui' import { Button, Input, Notification, toast } from '@/components/ui'
import Container from '@/components/shared/Container' import Container from '@/components/shared/Container'
import { WizardFileInfoDto } from '@/proxy/admin/list-form/models' import { WizardFileInfoDto } from '@/proxy/admin/list-form/models'
import { FaTrash, FaSync, FaDatabase, FaPlus, FaExclamationTriangle, FaSearch } from 'react-icons/fa' import {
FaTrash,
FaSync,
FaDatabase,
FaPlus,
FaExclamationTriangle,
FaSearch,
} from 'react-icons/fa'
import { deleteWizardFile, getWizardFiles } from '@/services/wizard.service' import { deleteWizardFile, getWizardFiles } from '@/services/wizard.service'
import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon' import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
@ -56,7 +63,9 @@ const WizardFileManager = () => {
setFiles(res.data ?? []) setFiles(res.data ?? [])
} catch { } catch {
toast.push( toast.push(
<Notification type="danger">Wizard dosyaları yüklenemedi.</Notification>, <Notification type="danger">
{translate('::App.Listforms.WizardFileLoadError') || 'Failed to load wizard files.'}
</Notification>,
{ placement: 'top-end' }, { placement: 'top-end' },
) )
} finally { } finally {
@ -76,7 +85,9 @@ const WizardFileManager = () => {
await deleteWizardFile(confirm.fileName) await deleteWizardFile(confirm.fileName)
toast.push( toast.push(
<Notification type="success" duration={3000}> <Notification type="success" duration={3000}>
<strong>{confirm.wizardName}</strong> silindi. <strong>{confirm.wizardName}</strong>{' '}
{translate('::App.Listforms.WizardFileDeleteSuccess') ||
'wizard file deleted successfully.'}
</Notification>, </Notification>,
{ placement: 'top-end' }, { placement: 'top-end' },
) )
@ -84,7 +95,8 @@ const WizardFileManager = () => {
} catch (err: any) { } catch (err: any) {
toast.push( toast.push(
<Notification type="danger"> <Notification type="danger">
Silme başarısız: {err?.message ?? 'Bilinmeyen hata'} {translate('::App.Listforms.WizardFileDeleteError') || 'Failed to delete wizard file.'}:{' '}
{err?.message ?? 'Unknown error'}
</Notification>, </Notification>,
{ placement: 'top-end' }, { placement: 'top-end' },
) )
@ -134,102 +146,99 @@ const WizardFileManager = () => {
className="flex items-center" className="flex items-center"
> >
<FaPlus className="mr-1" /> <FaPlus className="mr-1" />
{translate('::ListForms.ListForm.AddNewRecord') || 'Add New Record'} {translate('::ListForms.ListForm.AddNewRecord') || 'Add New Record'}
</Button> </Button>
</div> </div>
</div> </div>
<div className="mt-4"> <div className="mt-4">
{filteredFiles.length === 0 && !loading && (
<p className="text-xs text-gray-400 text-center py-4">
{translate('::App.Listforms.WizardNoFiles') || 'No wizard files found.'}
</p>
)}
{filteredFiles.length === 0 && !loading && ( {loading && (
<p className="text-xs text-gray-400 text-center py-4"> <p className="text-xs text-gray-400 text-center py-4 animate-pulse">
Henüz kaydedilmiş wizard dosyası yok. {translate('::App.Platform.Loading') || 'Loading...'}
</p> </p>
)} )}
{loading && ( <div className="space-y-2">
<p className="text-xs text-gray-400 text-center py-4 animate-pulse">Yükleniyor...</p> {filteredFiles.map((f) => (
)} <div
key={f.fileName}
<div className="space-y-2"> 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"
{filteredFiles.map((f) => ( >
<div <div className="flex items-center gap-3 min-w-0">
key={f.fileName} <FaDatabase className="text-indigo-400 shrink-0 text-lg" />
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="min-w-0">
> <div className="font-medium text-sm text-gray-800 dark:text-gray-200 truncate">
<div className="flex items-center gap-3 min-w-0"> {f.wizardName || f.fileName}
<FaDatabase className="text-indigo-400 shrink-0 text-lg" /> </div>
<div className="min-w-0"> <div className="text-xs text-gray-400 flex gap-3 mt-0.5">
<div className="font-medium text-sm text-gray-800 dark:text-gray-200 truncate"> <span>{formatTimestamp(f.createdAt)}</span>
{f.wizardName || f.fileName} <span className="truncate">{f.listFormCode}</span>
</div> </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>
</div>
<div className="flex items-center gap-2 shrink-0 ml-3"> <div className="flex items-center gap-2 shrink-0 ml-3">
{!f.hasInsertedRecords && ( {!f.hasInsertedRecords && (
<span <span
title="Bu dosyada izlenen kayıt bilgisi yok. Eski format olabilir." title="Bu dosyada izlenen kayıt bilgisi yok. Eski format olabilir."
className="text-yellow-500 text-xs flex items-center gap-1" className="text-yellow-500 text-xs flex items-center gap-1"
>
<FaExclamationTriangle />
</span>
)}
<Button
size="sm"
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 })
}
> >
<FaExclamationTriangle /> <FaTrash />
</span> </Button>
)}
<Button
size="sm"
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> </div>
<div className="flex justify-end gap-2 mt-4"> ))}
<Button </div>
size="sm"
variant="plain" {/* Confirm Dialog */}
type="button" {confirm && (
onClick={() => setConfirm(null)} <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">
İptal <div className="flex items-center gap-3 mb-4">
</Button> <FaExclamationTriangle className="text-red-500 text-xl shrink-0" />
<Button <div>
size="sm" <p className="font-semibold text-gray-800 dark:text-gray-200">{translate('::App.Platform.DeleteAction')}</p>
variant="solid" <p className="text-sm text-gray-500 mt-1">
color="red" {translate('::App.Listforms.WizardFileDeleteConfirm')}
type="button" </p>
onClick={handleDeleteConfirm} </div>
> </div>
Evet, Sil <div className="flex justify-end gap-2 mt-4">
</Button> <Button size="sm" variant="plain" type="button" onClick={() => setConfirm(null)}>
İptal
</Button>
<Button
size="sm"
variant="solid"
color="red"
type="button"
onClick={handleDeleteConfirm}
>
{translate('::App.Platform.Delete') || 'Yes, Delete'}
</Button>
</div>
</div> </div>
</div> </div>
</div> )}
)}
</div> </div>
</Container> </Container>
) )