Developer Kit düzenlemeleri
This commit is contained in:
parent
a746fea95e
commit
cc4efd4396
60 changed files with 1627 additions and 5232 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
public static class Prefix
|
||||
{
|
||||
public static string MenuPrefix { get; set; } = "Sqm";
|
||||
public static string MenuPrefix { get; set; } = "Sas";
|
||||
public static string HostPrefix { get; set; } = "T";
|
||||
public static string? DbSchema { get; set; } = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
// Migration DTOs
|
||||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Sozsoft.Platform.DeveloperKit;
|
||||
|
||||
public class CrudMigrationDto : AuditedEntityDto<Guid>
|
||||
{
|
||||
public Guid EntityId { get; set; }
|
||||
public string EntityName { get; set; } = string.Empty;
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public string SqlScript { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = "Askıda"; // "pending" | "applied" | "failed"
|
||||
public DateTime? AppliedAt { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
public class CreateUpdateCrudMigrationDto
|
||||
{
|
||||
public Guid EntityId { get; set; }
|
||||
public string EntityName { get; set; } = string.Empty;
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public string SqlScript { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = "Askıda";
|
||||
public DateTime? AppliedAt { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
|
||||
namespace Sozsoft.Platform.DeveloperKit;
|
||||
|
||||
public interface ICrudMigrationAppService : ICrudAppService<
|
||||
CrudMigrationDto,
|
||||
Guid,
|
||||
PagedAndSortedResultRequestDto,
|
||||
CreateUpdateCrudMigrationDto>
|
||||
{
|
||||
Task<CrudMigrationDto> ApplyMigrationAsync(Guid id);
|
||||
Task<List<CrudMigrationDto>> GetPendingMigrationsAsync();
|
||||
Task<CrudMigrationDto> GenerateMigrationAsync(Guid entityId);
|
||||
}
|
||||
|
||||
|
|
@ -6,13 +6,13 @@ using Volo.Abp.Application.Services;
|
|||
|
||||
namespace Sozsoft.Platform.DeveloperKit;
|
||||
|
||||
public interface ICustomEntityAppService : ICrudAppService<
|
||||
CustomEntityDto,
|
||||
public interface ISqlTableAppService : ICrudAppService<
|
||||
SqlTableDto,
|
||||
Guid,
|
||||
PagedAndSortedResultRequestDto,
|
||||
CreateUpdateCustomEntityDto>
|
||||
CreateUpdateSqlTableDto>
|
||||
{
|
||||
Task<List<CustomEntityDto>> GetActiveEntitiesAsync();
|
||||
Task<CustomEntityDto> ToggleActiveStatusAsync(Guid id);
|
||||
Task<List<SqlTableDto>> GetActiveEntitiesAsync();
|
||||
Task<SqlTableDto> ToggleActiveStatusAsync(Guid id);
|
||||
}
|
||||
|
||||
|
|
@ -4,7 +4,7 @@ using Volo.Abp.Application.Dtos;
|
|||
|
||||
namespace Sozsoft.Platform.DeveloperKit;
|
||||
|
||||
public class CustomEntityDto : FullAuditedEntityDto<Guid>
|
||||
public class SqlTableDto : FullAuditedEntityDto<Guid>
|
||||
{
|
||||
public string Menu { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
|
@ -14,14 +14,12 @@ public class CustomEntityDto : FullAuditedEntityDto<Guid>
|
|||
public bool IsActive { get; set; } = true;
|
||||
public bool IsFullAuditedEntity { get; set; } = true;
|
||||
public bool IsMultiTenant { get; set; } = false;
|
||||
public string MigrationStatus { get; set; } = "Askıda";
|
||||
public Guid? MigrationId { get; set; }
|
||||
public string EndpointStatus { get; set; } = "Askıda";
|
||||
public string EndpointStatus { get; set; } = "pending";
|
||||
|
||||
public List<EntityFieldDto> Fields { get; set; } = [];
|
||||
public List<SqlTableFieldDto> Fields { get; set; } = [];
|
||||
}
|
||||
|
||||
public class CreateUpdateCustomEntityDto
|
||||
public class CreateUpdateSqlTableDto
|
||||
{
|
||||
public string Menu { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
|
@ -32,10 +30,10 @@ public class CreateUpdateCustomEntityDto
|
|||
public bool IsFullAuditedEntity { get; set; } = true;
|
||||
public bool IsMultiTenant { get; set; } = false;
|
||||
|
||||
public List<CreateUpdateCustomEntityFieldDto> Fields { get; set; } = [];
|
||||
public List<CreateUpdateSqlTableFieldDto> Fields { get; set; } = [];
|
||||
}
|
||||
|
||||
public class EntityFieldDto : FullAuditedEntityDto<Guid>
|
||||
public class SqlTableFieldDto : FullAuditedEntityDto<Guid>
|
||||
{
|
||||
public Guid EntityId { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
|
@ -48,7 +46,7 @@ public class EntityFieldDto : FullAuditedEntityDto<Guid>
|
|||
public int DisplayOrder { get; set; } = 0;
|
||||
}
|
||||
|
||||
public class CreateUpdateCustomEntityFieldDto
|
||||
public class CreateUpdateSqlTableFieldDto
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
|
||||
|
|
@ -18,19 +18,16 @@ public class CrudEndpointGenerateAppService : CrudAppService<
|
|||
PagedAndSortedResultRequestDto,
|
||||
CreateUpdateCrudEndpointDto>, ICrudEndpointAppService
|
||||
{
|
||||
private readonly IRepository<CustomEntity, Guid> _entityRepository;
|
||||
private readonly IRepository<CrudMigration, Guid> _migrationRepository;
|
||||
private readonly IRepository<SqlTable, Guid> _entityRepository;
|
||||
private readonly IRepository<CrudEndpoint, Guid> _endpointRepository;
|
||||
|
||||
public CrudEndpointGenerateAppService(
|
||||
IRepository<CrudEndpoint, Guid> repository,
|
||||
IRepository<CustomEntity, Guid> entityRepository,
|
||||
IRepository<CrudMigration, Guid> migrationRepository,
|
||||
IRepository<SqlTable, Guid> entityRepository,
|
||||
IRepository<CrudEndpoint, Guid> endpointRepository)
|
||||
: base(repository)
|
||||
{
|
||||
_entityRepository = entityRepository;
|
||||
_migrationRepository = migrationRepository;
|
||||
_endpointRepository = endpointRepository;
|
||||
}
|
||||
|
||||
|
|
@ -59,17 +56,6 @@ public class CrudEndpointGenerateAppService : CrudAppService<
|
|||
throw new Exception($"Entity with ID {entityId} not found");
|
||||
}
|
||||
|
||||
// Migration kontrolü
|
||||
var migrationQueryable = await _migrationRepository.GetQueryableAsync();
|
||||
var migration = await migrationQueryable
|
||||
.Where(x => x.EntityId == entityId && x.Status == "applied")
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (migration == null)
|
||||
{
|
||||
throw new Exception($"No applied migration found for entity {entity.Name}. Please apply migration first.");
|
||||
}
|
||||
|
||||
// CRUD endpointleri oluştur
|
||||
var endpoints = new List<CrudEndpoint>();
|
||||
var entityName = entity.Name;
|
||||
|
|
|
|||
|
|
@ -1,302 +0,0 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using System.Text;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Sozsoft.Platform.Entities;
|
||||
using Sozsoft.Platform.DeveloperKit;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Sozsoft.Platform.Domain.DeveloperKit;
|
||||
|
||||
namespace Platform.Api.Application;
|
||||
|
||||
public class CrudMigrationAppService : CrudAppService<
|
||||
CrudMigration,
|
||||
CrudMigrationDto,
|
||||
Guid,
|
||||
PagedAndSortedResultRequestDto,
|
||||
CreateUpdateCrudMigrationDto>, ICrudMigrationAppService
|
||||
{
|
||||
private readonly IRepository<CrudMigration, Guid> _migrationRepository;
|
||||
private readonly IRepository<CustomEntity, Guid> _entityRepository;
|
||||
private readonly IRepository<CustomEntity, Guid> _fieldRepository;
|
||||
private readonly IApiMigrationRepository _customSqlExecutor;
|
||||
|
||||
public CrudMigrationAppService(
|
||||
IRepository<CrudMigration, Guid> migrationRepository,
|
||||
IRepository<CustomEntity, Guid> entityRepository,
|
||||
IRepository<CustomEntity, Guid> fieldRepository,
|
||||
IApiMigrationRepository customSqlExecutor
|
||||
) : base(migrationRepository)
|
||||
{
|
||||
_migrationRepository = migrationRepository;
|
||||
_entityRepository = entityRepository;
|
||||
_fieldRepository = fieldRepository;
|
||||
_customSqlExecutor = customSqlExecutor;
|
||||
}
|
||||
|
||||
public virtual async Task<CrudMigrationDto> ApplyMigrationAsync(Guid id)
|
||||
{
|
||||
var migration = await _migrationRepository.GetAsync(id);
|
||||
|
||||
try
|
||||
{
|
||||
await _customSqlExecutor.ExecuteSqlAsync(migration.SqlScript);
|
||||
|
||||
migration.Status = "Uygulandı";
|
||||
migration.AppliedAt = DateTime.UtcNow;
|
||||
migration.ErrorMessage = null;
|
||||
|
||||
await _migrationRepository.UpdateAsync(migration, autoSave: true);
|
||||
|
||||
var entity = await _entityRepository.GetAsync(migration.EntityId);
|
||||
entity.MigrationStatus = "Uygulandı";
|
||||
await _entityRepository.UpdateAsync(entity, autoSave: true);
|
||||
|
||||
return ObjectMapper.Map<CrudMigration, CrudMigrationDto>(migration);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
migration.Status = "failed";
|
||||
migration.ErrorMessage = ex.Message;
|
||||
await _migrationRepository.UpdateAsync(migration, autoSave: true);
|
||||
|
||||
var entity = await _entityRepository.GetAsync(migration.EntityId);
|
||||
entity.MigrationStatus = "failed";
|
||||
await _entityRepository.UpdateAsync(entity, autoSave: true);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task<List<CrudMigrationDto>> GetPendingMigrationsAsync()
|
||||
{
|
||||
var queryable = await _migrationRepository.GetQueryableAsync();
|
||||
var pending = await queryable
|
||||
.Where(m => m.Status == "pending")
|
||||
.OrderBy(m => m.CreationTime)
|
||||
.ToListAsync();
|
||||
|
||||
return ObjectMapper.Map<List<CrudMigration>, List<CrudMigrationDto>>(pending);
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task<CrudMigrationDto> GenerateMigrationAsync(Guid entityId)
|
||||
{
|
||||
var queryable = await _entityRepository.GetQueryableAsync();
|
||||
var entity = await queryable
|
||||
.Include(x => x.Fields)
|
||||
.FirstOrDefaultAsync(x => x.Id == entityId);
|
||||
|
||||
if (entity == null)
|
||||
throw new EntityNotFoundException($"CustomEntity with id {entityId} not found");
|
||||
|
||||
// Check if there are any previously applied migrations for this entity
|
||||
var migrationQueryable = await _migrationRepository.GetQueryableAsync();
|
||||
var lastAppliedMigration = await migrationQueryable
|
||||
.Where(m => m.EntityId == entityId && m.Status == "applied")
|
||||
.OrderByDescending(m => m.CreationTime)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
// Use "Update" if migrations were applied before, otherwise "Create"
|
||||
var operationType = lastAppliedMigration != null ? "Update" : "Create";
|
||||
var fileName = $"{DateTime.UtcNow:yyyyMMddHHmmss}_{operationType}{entity.Name}Table.sql";
|
||||
|
||||
var sqlScript = lastAppliedMigration != null
|
||||
? GenerateAlterTableScript(entity, lastAppliedMigration)
|
||||
: GenerateCreateTableScript(entity);
|
||||
|
||||
var migration = new CrudMigration
|
||||
{
|
||||
EntityId = entityId,
|
||||
EntityName = entity.Name,
|
||||
FileName = fileName,
|
||||
SqlScript = sqlScript,
|
||||
Status = "Askıda"
|
||||
};
|
||||
|
||||
migration = await _migrationRepository.InsertAsync(migration, autoSave: true);
|
||||
|
||||
entity.MigrationId = migration.Id;
|
||||
entity.MigrationStatus = "Askıda";
|
||||
await _entityRepository.UpdateAsync(entity, autoSave: true);
|
||||
|
||||
return ObjectMapper.Map<CrudMigration, CrudMigrationDto>(migration);
|
||||
}
|
||||
|
||||
private string GenerateCreateTableScript(CustomEntity entity)
|
||||
{
|
||||
if (entity.Fields == null || !entity.Fields.Any())
|
||||
throw new InvalidOperationException($"Entity '{entity.Name}' does not have any fields defined.");
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine($@"/*
|
||||
# Create {entity.DisplayName} table
|
||||
|
||||
1. New Table
|
||||
- [{entity.TableName}]");
|
||||
|
||||
foreach (var field in entity.Fields)
|
||||
{
|
||||
sb.AppendLine($" - [{field.Name}] ({field.Type}{(field.IsRequired ? ", required" : "")})");
|
||||
}
|
||||
|
||||
sb.AppendLine("*/");
|
||||
|
||||
sb.AppendLine($"\nIF OBJECT_ID(N'{entity.TableName}', N'U') IS NULL");
|
||||
sb.AppendLine($"CREATE TABLE [{entity.TableName}] (");
|
||||
sb.AppendLine(" [Id] UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),");
|
||||
|
||||
foreach (var field in entity.Fields)
|
||||
{
|
||||
var sqlType = GetSqlType(field.Type, field.MaxLength);
|
||||
sb.Append($" [{field.Name}] {sqlType}");
|
||||
|
||||
if (field.IsRequired)
|
||||
sb.Append(" NOT NULL");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(field.DefaultValue))
|
||||
sb.Append($" DEFAULT {FormatDefaultValue(field.Type, field.DefaultValue)}");
|
||||
|
||||
sb.AppendLine(",");
|
||||
}
|
||||
|
||||
// Add TenantId if entity is multi-tenant
|
||||
if (entity.IsMultiTenant)
|
||||
{
|
||||
sb.AppendLine(" [TenantId] UNIQUEIDENTIFIER NULL,");
|
||||
}
|
||||
|
||||
// Add Full Audited Entity fields (includes both audit and soft delete)
|
||||
if (entity.IsFullAuditedEntity)
|
||||
{
|
||||
sb.AppendLine(" [CreationTime] DATETIME2 DEFAULT SYSUTCDATETIME() NOT NULL,");
|
||||
sb.AppendLine(" [CreatorId] UNIQUEIDENTIFIER NULL,");
|
||||
sb.AppendLine(" [LastModificationTime] DATETIME2 NULL,");
|
||||
sb.AppendLine(" [LastModifierId] UNIQUEIDENTIFIER NULL,");
|
||||
sb.AppendLine(" [IsDeleted] BIT DEFAULT 0 NOT NULL,");
|
||||
sb.AppendLine(" [DeleterId] UNIQUEIDENTIFIER NULL,");
|
||||
sb.AppendLine(" [DeletionTime] DATETIME2 NULL,");
|
||||
}
|
||||
|
||||
// Remove last comma
|
||||
var script = sb.ToString().TrimEnd(',', '\r', '\n');
|
||||
script += "\n);\n";
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
private string GenerateAlterTableScript(CustomEntity entity, CrudMigration lastAppliedMigration)
|
||||
{
|
||||
if (entity.Fields == null || !entity.Fields.Any())
|
||||
throw new InvalidOperationException($"Entity '{entity.Name}' does not have any fields defined.");
|
||||
|
||||
// Parse the last applied migration's SQL to understand what fields existed
|
||||
var existingFieldNames = ExtractFieldNamesFromCreateScript(lastAppliedMigration.SqlScript);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine($@"/*
|
||||
# Update {entity.DisplayName} table
|
||||
|
||||
1. Alter Table
|
||||
- [{entity.TableName}]
|
||||
- Adding new fields or modifying existing ones");
|
||||
|
||||
sb.AppendLine("*/\n");
|
||||
|
||||
// Find new fields that need to be added
|
||||
var newFields = entity.Fields.Where(f => !existingFieldNames.Contains(f.Name)).ToList();
|
||||
|
||||
if (newFields.Any())
|
||||
{
|
||||
foreach (var field in newFields)
|
||||
{
|
||||
var sqlType = GetSqlType(field.Type, field.MaxLength);
|
||||
sb.Append($"IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[{entity.TableName}]') AND name = '{field.Name}')\n");
|
||||
sb.Append($"BEGIN\n");
|
||||
sb.Append($" ALTER TABLE [{entity.TableName}] ADD [{field.Name}] {sqlType}");
|
||||
|
||||
if (field.IsRequired)
|
||||
sb.Append(" NOT NULL");
|
||||
else
|
||||
sb.Append(" NULL");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(field.DefaultValue))
|
||||
sb.Append($" DEFAULT {FormatDefaultValue(field.Type, field.DefaultValue)}");
|
||||
|
||||
sb.AppendLine(";");
|
||||
sb.AppendLine($"END\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("-- No new fields to add");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private HashSet<string> ExtractFieldNamesFromCreateScript(string sqlScript)
|
||||
{
|
||||
var fieldNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Simple parsing - extract field names between [ and ]
|
||||
var lines = sqlScript.Split('\n');
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var trimmedLine = line.Trim();
|
||||
if (trimmedLine.StartsWith("[") && trimmedLine.Contains("]"))
|
||||
{
|
||||
var fieldName = trimmedLine.Substring(1, trimmedLine.IndexOf("]") - 1);
|
||||
if (fieldName != "Id" &&
|
||||
fieldName != "TenantId" &&
|
||||
fieldName != "CreationTime" &&
|
||||
fieldName != "CreatorId" &&
|
||||
fieldName != "LastModificationTime" &&
|
||||
fieldName != "LastModifierId" &&
|
||||
fieldName != "IsDeleted" &&
|
||||
fieldName != "DeleterId" &&
|
||||
fieldName != "DeletionTime")
|
||||
{
|
||||
fieldNames.Add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fieldNames;
|
||||
}
|
||||
|
||||
private string GetSqlType(string fieldType, int? maxLength)
|
||||
{
|
||||
return fieldType.ToLower() switch
|
||||
{
|
||||
"string" => maxLength.HasValue ? $"NVARCHAR({maxLength.Value})" : "NVARCHAR(MAX)",
|
||||
"number" => "INT",
|
||||
"decimal" => "DECIMAL(18, 2)",
|
||||
"boolean" => "BIT",
|
||||
"date" => "DATETIME2",
|
||||
"guid" => "UNIQUEIDENTIFIER",
|
||||
_ => "NVARCHAR(MAX)"
|
||||
};
|
||||
}
|
||||
|
||||
private string FormatDefaultValue(string fieldType, string defaultValue)
|
||||
{
|
||||
return fieldType.ToLower() switch
|
||||
{
|
||||
"string" => $"N'{defaultValue}'",
|
||||
"date" => $"'{defaultValue}'", // ISO format beklenir
|
||||
"guid" => $"'{defaultValue}'",
|
||||
"boolean" => (defaultValue.ToLower() == "true" || defaultValue == "1") ? "1" : "0",
|
||||
_ => defaultValue
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7,13 +7,13 @@ public class DeveloperKitAutoMapperProfile : Profile
|
|||
{
|
||||
public DeveloperKitAutoMapperProfile()
|
||||
{
|
||||
// CustomEntity mappings
|
||||
CreateMap<CustomEntity, CustomEntityDto>();
|
||||
CreateMap<CreateUpdateCustomEntityDto, CustomEntity>();
|
||||
// SqlTable mappings
|
||||
CreateMap<SqlTable, SqlTableDto>();
|
||||
CreateMap<CreateUpdateSqlTableDto, SqlTable>();
|
||||
|
||||
// EntityField mappings
|
||||
CreateMap<CustomEntityField, EntityFieldDto>();
|
||||
CreateMap<CreateUpdateCustomEntityFieldDto, CustomEntityField>();
|
||||
CreateMap<SqlTableField, SqlTableFieldDto>();
|
||||
CreateMap<CreateUpdateSqlTableFieldDto, SqlTableField>();
|
||||
|
||||
// CustomComponent mappings
|
||||
CreateMap<CustomComponent, CustomComponentDto>();
|
||||
|
|
@ -24,10 +24,6 @@ public class DeveloperKitAutoMapperProfile : Profile
|
|||
.ForMember(dest => dest.EntityDisplayName, opt => opt.MapFrom(src => src.Entity != null ? src.Entity.DisplayName : null));
|
||||
CreateMap<CreateUpdateCrudEndpointDto, CrudEndpoint>();
|
||||
|
||||
// Migration mappings
|
||||
CreateMap<CrudMigration, CrudMigrationDto>();
|
||||
CreateMap<CreateUpdateCrudMigrationDto, CrudMigration>();
|
||||
|
||||
CreateMap<DynamicService, DynamicServiceDto>()
|
||||
.ForMember(dest => dest.CompilationStatus, opt => opt.MapFrom(src => src.CompilationStatus.ToString()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,35 +9,31 @@ using System;
|
|||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Platform.Api.Application;
|
||||
|
||||
public class CustomEntityAppService : CrudAppService<
|
||||
CustomEntity,
|
||||
CustomEntityDto,
|
||||
public class SqlTableAppService : CrudAppService<
|
||||
SqlTable,
|
||||
SqlTableDto,
|
||||
Guid,
|
||||
PagedAndSortedResultRequestDto,
|
||||
CreateUpdateCustomEntityDto>, ICustomEntityAppService
|
||||
CreateUpdateSqlTableDto>, ISqlTableAppService
|
||||
{
|
||||
private readonly IRepository<CustomEntity, Guid> _repository;
|
||||
private readonly IRepository<CrudMigration, Guid> _migrationRepository;
|
||||
private readonly IRepository<SqlTable, Guid> _repository;
|
||||
private readonly IRepository<SqlTableField, Guid> _fieldRepository;
|
||||
private readonly IRepository<CrudEndpoint, Guid> _endpointRepository;
|
||||
private readonly IRepository<CustomEntityField, Guid> _fieldRepository;
|
||||
|
||||
public CustomEntityAppService(
|
||||
IRepository<CustomEntity, Guid> repository,
|
||||
IRepository<CrudMigration, Guid> migrationRepository,
|
||||
IRepository<CrudEndpoint, Guid> endpointRepository,
|
||||
IRepository<CustomEntityField, Guid> fieldRepository) : base(repository)
|
||||
public SqlTableAppService(
|
||||
IRepository<SqlTable, Guid> repository,
|
||||
IRepository<SqlTableField, Guid> fieldRepository,
|
||||
IRepository<CrudEndpoint, Guid> endpointRepository) : base(repository)
|
||||
{
|
||||
_repository = repository;
|
||||
_migrationRepository = migrationRepository;
|
||||
_endpointRepository = endpointRepository;
|
||||
_fieldRepository = fieldRepository;
|
||||
_endpointRepository = endpointRepository;
|
||||
}
|
||||
|
||||
public override async Task<PagedResultDto<CustomEntityDto>> GetListAsync(PagedAndSortedResultRequestDto input)
|
||||
public override async Task<PagedResultDto<SqlTableDto>> GetListAsync(PagedAndSortedResultRequestDto input)
|
||||
{
|
||||
var query = await _repository.GetQueryableAsync();
|
||||
var fullQuery = query.Include(x => x.Fields.OrderBy(f => f.DisplayOrder));
|
||||
|
|
@ -50,12 +46,12 @@ public class CustomEntityAppService : CrudAppService<
|
|||
.Take(input.MaxResultCount)
|
||||
.ToListAsync();
|
||||
|
||||
var dtos = ObjectMapper.Map<List<CustomEntity>, List<CustomEntityDto>>(entities);
|
||||
var dtos = ObjectMapper.Map<List<SqlTable>, List<SqlTableDto>>(entities);
|
||||
|
||||
return new PagedResultDto<CustomEntityDto>(totalCount, dtos);
|
||||
return new PagedResultDto<SqlTableDto>(totalCount, dtos);
|
||||
}
|
||||
|
||||
public override async Task<CustomEntityDto> GetAsync(Guid id)
|
||||
public override async Task<SqlTableDto> GetAsync(Guid id)
|
||||
{
|
||||
var query = await _repository.GetQueryableAsync();
|
||||
var entity = await query
|
||||
|
|
@ -65,10 +61,10 @@ public class CustomEntityAppService : CrudAppService<
|
|||
if (entity == null)
|
||||
throw new EntityNotFoundException($"CustomEntity with id {id} not found");
|
||||
|
||||
return ObjectMapper.Map<CustomEntity, CustomEntityDto>(entity);
|
||||
return ObjectMapper.Map<SqlTable, SqlTableDto>(entity);
|
||||
}
|
||||
|
||||
public async Task<List<CustomEntityDto>> GetActiveEntitiesAsync()
|
||||
public async Task<List<SqlTableDto>> GetActiveEntitiesAsync()
|
||||
{
|
||||
var query = await _repository.GetQueryableAsync();
|
||||
var entities = await query
|
||||
|
|
@ -76,10 +72,10 @@ public class CustomEntityAppService : CrudAppService<
|
|||
.Where(x => x.IsActive)
|
||||
.ToListAsync();
|
||||
|
||||
return ObjectMapper.Map<List<CustomEntity>, List<CustomEntityDto>>(entities);
|
||||
return ObjectMapper.Map<List<SqlTable>, List<SqlTableDto>>(entities);
|
||||
}
|
||||
|
||||
public async Task<CustomEntityDto> ToggleActiveStatusAsync(Guid id)
|
||||
public async Task<SqlTableDto> ToggleActiveStatusAsync(Guid id)
|
||||
{
|
||||
var query = await _repository.GetQueryableAsync();
|
||||
var entity = await query
|
||||
|
|
@ -92,10 +88,10 @@ public class CustomEntityAppService : CrudAppService<
|
|||
entity.IsActive = !entity.IsActive;
|
||||
await _repository.UpdateAsync(entity, autoSave: true);
|
||||
|
||||
return ObjectMapper.Map<CustomEntity, CustomEntityDto>(entity);
|
||||
return ObjectMapper.Map<SqlTable, SqlTableDto>(entity);
|
||||
}
|
||||
|
||||
public override async Task<CustomEntityDto> UpdateAsync(Guid id, CreateUpdateCustomEntityDto input)
|
||||
public override async Task<SqlTableDto> UpdateAsync(Guid id, CreateUpdateSqlTableDto input)
|
||||
{
|
||||
var query = await _repository.GetQueryableAsync();
|
||||
var entity = await query
|
||||
|
|
@ -103,20 +99,7 @@ public class CustomEntityAppService : CrudAppService<
|
|||
.FirstOrDefaultAsync(e => e.Id == id);
|
||||
|
||||
if (entity == null)
|
||||
throw new EntityNotFoundException(typeof(CustomEntity), id);
|
||||
|
||||
// If entity structure has changed and migration was applied, reset migration status
|
||||
bool structureChanged = entity.Name != input.Name ||
|
||||
entity.TableName != input.TableName ||
|
||||
entity.IsFullAuditedEntity != input.IsFullAuditedEntity ||
|
||||
entity.IsMultiTenant != input.IsMultiTenant ||
|
||||
FieldsHaveChanged(entity.Fields, input.Fields);
|
||||
|
||||
if (structureChanged && entity.MigrationStatus == "applied")
|
||||
{
|
||||
entity.MigrationStatus = "Askıda";
|
||||
entity.MigrationId = null;
|
||||
}
|
||||
throw new EntityNotFoundException(typeof(SqlTable), id);
|
||||
|
||||
entity.Menu = input.Menu;
|
||||
entity.Name = input.Name;
|
||||
|
|
@ -127,12 +110,12 @@ public class CustomEntityAppService : CrudAppService<
|
|||
entity.IsFullAuditedEntity = input.IsFullAuditedEntity;
|
||||
entity.IsMultiTenant = input.IsMultiTenant;
|
||||
|
||||
var updatedFields = new List<CustomEntityField>();
|
||||
var updatedFields = new List<SqlTableField>();
|
||||
|
||||
for (int i = 0; i < input.Fields.Count; i++)
|
||||
{
|
||||
var dtoField = input.Fields[i];
|
||||
CustomEntityField? existingField = null;
|
||||
SqlTableField? existingField = null;
|
||||
|
||||
if (dtoField.Id.HasValue)
|
||||
{
|
||||
|
|
@ -154,7 +137,7 @@ public class CustomEntityAppService : CrudAppService<
|
|||
}
|
||||
else
|
||||
{
|
||||
var newField = new CustomEntityField
|
||||
var newField = new SqlTableField
|
||||
{
|
||||
EntityId = entity.Id,
|
||||
Name = dtoField.Name,
|
||||
|
|
@ -184,13 +167,13 @@ public class CustomEntityAppService : CrudAppService<
|
|||
|
||||
await _repository.UpdateAsync(entity, autoSave: true);
|
||||
|
||||
return ObjectMapper.Map<CustomEntity, CustomEntityDto>(entity);
|
||||
return ObjectMapper.Map<SqlTable, SqlTableDto>(entity);
|
||||
}
|
||||
|
||||
public override async Task<CustomEntityDto> CreateAsync(CreateUpdateCustomEntityDto input)
|
||||
public override async Task<SqlTableDto> CreateAsync(CreateUpdateSqlTableDto input)
|
||||
{
|
||||
// Entity oluştur
|
||||
var entity = new CustomEntity
|
||||
var entity = new SqlTable
|
||||
{
|
||||
Menu = input.Menu,
|
||||
Name = input.Name,
|
||||
|
|
@ -200,14 +183,13 @@ public class CustomEntityAppService : CrudAppService<
|
|||
IsActive = input.IsActive,
|
||||
IsFullAuditedEntity = input.IsFullAuditedEntity,
|
||||
IsMultiTenant = input.IsMultiTenant,
|
||||
MigrationStatus = "Askıda"
|
||||
};
|
||||
|
||||
// Fields ekle - sıralama ile
|
||||
for (int i = 0; i < input.Fields.Count; i++)
|
||||
{
|
||||
var fieldDto = input.Fields[i];
|
||||
var field = new CustomEntityField
|
||||
var field = new SqlTableField
|
||||
{
|
||||
EntityId = entity.Id,
|
||||
Name = fieldDto.Name,
|
||||
|
|
@ -224,10 +206,10 @@ public class CustomEntityAppService : CrudAppService<
|
|||
|
||||
entity = await _repository.InsertAsync(entity, autoSave: true);
|
||||
|
||||
return ObjectMapper.Map<CustomEntity, CustomEntityDto>(entity);
|
||||
return ObjectMapper.Map<SqlTable, SqlTableDto>(entity);
|
||||
}
|
||||
|
||||
private bool FieldsHaveChanged(ICollection<CustomEntityField> existingFields, List<CreateUpdateCustomEntityFieldDto> inputFields)
|
||||
private bool FieldsHaveChanged(ICollection<SqlTableField> existingFields, List<CreateUpdateSqlTableFieldDto> inputFields)
|
||||
{
|
||||
if (existingFields.Count != inputFields.Count)
|
||||
return true;
|
||||
|
|
@ -252,31 +234,5 @@ public class CustomEntityAppService : CrudAppService<
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override async Task DeleteAsync(Guid id)
|
||||
{
|
||||
// İlgili entity'nin var olup olmadığını kontrol et
|
||||
var entity = await _repository.GetAsync(id);
|
||||
|
||||
// İlgili ApiMigration kayıtlarını sil
|
||||
var relatedMigrations = await _migrationRepository.GetListAsync(m => m.EntityId == id);
|
||||
if (relatedMigrations.Any())
|
||||
{
|
||||
Logger.LogInformation($"Deleting {relatedMigrations.Count} related migrations for entity {entity.Name} (ID: {id})");
|
||||
await _migrationRepository.DeleteManyAsync(relatedMigrations);
|
||||
}
|
||||
|
||||
var relatedEndpoints = await _endpointRepository.GetListAsync(e => e.EntityId == id);
|
||||
if (relatedEndpoints.Any())
|
||||
{
|
||||
Logger.LogInformation($"Deleting {relatedEndpoints.Count} related API endpoints for entity {entity.Name} (ID: {id})");
|
||||
await _endpointRepository.DeleteManyAsync(relatedEndpoints);
|
||||
}
|
||||
|
||||
Logger.LogInformation($"Deleting CustomEntity {entity.Name} (ID: {id}) and its fields");
|
||||
|
||||
// Ana entity'yi sil (CustomEntityField'lar otomatik silinecek - cascade delete)
|
||||
await base.DeleteAsync(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -624,12 +624,6 @@
|
|||
"en": "Custom Entity",
|
||||
"tr": "Özel Varlık"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.DeveloperKit.Migrations",
|
||||
"en": "Crud Migrations",
|
||||
"tr": "Crud Geçişleri"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.DeveloperKit.CrudEndpoints",
|
||||
|
|
|
|||
|
|
@ -283,84 +283,49 @@
|
|||
{
|
||||
"key": "admin.sqlQueryManager",
|
||||
"path": "/admin/sqlQueryManager",
|
||||
"componentPath": "@/views/sqlQueryManager/SqlQueryManager",
|
||||
"componentPath": "@/views/developerKit/SqlQueryManager",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.SqlQueryManager"]
|
||||
},
|
||||
{
|
||||
"key": "admin.developerkit",
|
||||
"path": "/admin/developerkit",
|
||||
"componentPath": "@/views/developerKit/DashboardPage",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.DeveloperKit"]
|
||||
},
|
||||
{
|
||||
"key": "admin.developerkit.entities",
|
||||
"path": "/admin/developerkit/entities",
|
||||
"componentPath": "@/views/developerKit/EntityPage",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.DeveloperKit.Entity"]
|
||||
},
|
||||
{
|
||||
"key": "admin.developerkit.entities.new",
|
||||
"path": "/admin/developerkit/entities/new",
|
||||
"componentPath": "@/views/developerKit/EntityDetailPage",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.DeveloperKit.Entity"]
|
||||
},
|
||||
{
|
||||
"key": "admin.developerkit.entities.edit",
|
||||
"path": "/admin/developerkit/entities/edit/:id",
|
||||
"componentPath": "@/views/developerKit/EntityDetailPage",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.DeveloperKit.Entity"]
|
||||
},
|
||||
{
|
||||
"key": "admin.developerkit.migrations",
|
||||
"path": "/admin/developerkit/migrations",
|
||||
"componentPath": "@/views/developerKit/MigrationPage",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.DeveloperKit.Migrations"]
|
||||
},
|
||||
{
|
||||
"key": "admin.developerkit.endpoints",
|
||||
"path": "/admin/developerkit/endpoints",
|
||||
"componentPath": "@/views/developerKit/CrudEndpointPage",
|
||||
"componentPath": "@/views/developerKit/CrudEndpointManager",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.DeveloperKit.CrudEndpoints"]
|
||||
},
|
||||
{
|
||||
"key": "admin.developerkit.dynamic-services",
|
||||
"path": "/admin/developerkit/dynamic-services",
|
||||
"componentPath": "@/views/developerKit/DynamicServicePage",
|
||||
"componentPath": "@/views/developerKit/DynamicServiceEditor",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.DeveloperKit.DynamicServices"]
|
||||
},
|
||||
{
|
||||
"key": "admin.developerkit.components",
|
||||
"path": "/admin/developerkit/components",
|
||||
"componentPath": "@/views/developerKit/ComponentPage",
|
||||
"componentPath": "@/views/developerKit/ComponentManagerPage",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.DeveloperKit.Components"]
|
||||
},
|
||||
{
|
||||
"key": "admin.developerkit.components.new",
|
||||
"path": "/admin/developerkit/components/new",
|
||||
"componentPath": "@/views/developerKit/ComponentDetailPage",
|
||||
"componentPath": "@/views/developerKit/ComponentEditorPage",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.DeveloperKit.Components"]
|
||||
},
|
||||
{
|
||||
"key": "admin.developerkit.components.view",
|
||||
"path": "/admin/developerkit/components/view/:id",
|
||||
"componentPath": "@/views/developerKit/ComponentDetailPage",
|
||||
"componentPath": "@/views/developerKit/ComponentEditorPage",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.DeveloperKit.Components"]
|
||||
},
|
||||
{
|
||||
"key": "admin.developerkit.components.edit",
|
||||
"path": "/admin/developerkit/components/edit/:id",
|
||||
"componentPath": "@/views/developerKit/CodePage",
|
||||
"componentPath": "@/views/developerKit/CodeLayout",
|
||||
"routeType": "protected",
|
||||
"authority": ["App.DeveloperKit.Components"]
|
||||
},
|
||||
|
|
@ -971,39 +936,19 @@
|
|||
},
|
||||
{
|
||||
"ParentCode": "App.DeveloperKit",
|
||||
"Code": "App.DeveloperKit.Dashboard",
|
||||
"DisplayName": "App.Coordinator.Classroom.Dashboard",
|
||||
"Code": "App.SqlQueryManager",
|
||||
"DisplayName": "App.SqlQueryManager",
|
||||
"Order": 1,
|
||||
"Url": "/admin/developerkit",
|
||||
"Icon": "FcBinoculars",
|
||||
"RequiredPermissionName": "App.DeveloperKit",
|
||||
"IsDisabled": false
|
||||
},
|
||||
{
|
||||
"ParentCode": "App.DeveloperKit",
|
||||
"Code": "App.DeveloperKit.Entity",
|
||||
"DisplayName": "App.DeveloperKit.Entity",
|
||||
"Order": 2,
|
||||
"Url": "/admin/developerkit/entities",
|
||||
"Icon": "FcAddRow",
|
||||
"RequiredPermissionName": "App.DeveloperKit.Entity",
|
||||
"IsDisabled": false
|
||||
},
|
||||
{
|
||||
"ParentCode": "App.DeveloperKit",
|
||||
"Code": "App.DeveloperKit.Migrations",
|
||||
"DisplayName": "App.DeveloperKit.Migrations",
|
||||
"Order": 3,
|
||||
"Url": "/admin/developerkit/migrations",
|
||||
"Icon": "FcAddDatabase",
|
||||
"RequiredPermissionName": "App.DeveloperKit.Migrations",
|
||||
"Url": "/admin/sqlQueryManager",
|
||||
"Icon": "FaDatabase",
|
||||
"RequiredPermissionName": "App.SqlQueryManager",
|
||||
"IsDisabled": false
|
||||
},
|
||||
{
|
||||
"ParentCode": "App.DeveloperKit",
|
||||
"Code": "App.DeveloperKit.CrudEndpoints",
|
||||
"DisplayName": "App.DeveloperKit.CrudEndpoints",
|
||||
"Order": 4,
|
||||
"Order": 2,
|
||||
"Url": "/admin/developerkit/endpoints",
|
||||
"Icon": "FcOrgUnit",
|
||||
"RequiredPermissionName": "App.DeveloperKit.CrudEndpoints",
|
||||
|
|
@ -1013,7 +958,7 @@
|
|||
"ParentCode": "App.DeveloperKit",
|
||||
"Code": "App.DeveloperKit.CustomEndpoints",
|
||||
"DisplayName": "App.DeveloperKit.CustomEndpoints",
|
||||
"Order": 5,
|
||||
"Order": 3,
|
||||
"Url": "/admin/list/App.DeveloperKit.CustomEndpoints",
|
||||
"Icon": "FcMindMap",
|
||||
"RequiredPermissionName": "App.DeveloperKit.CustomEndpoints",
|
||||
|
|
@ -1023,7 +968,7 @@
|
|||
"ParentCode": "App.DeveloperKit",
|
||||
"Code": "App.DeveloperKit.Components",
|
||||
"DisplayName": "App.DeveloperKit.Components",
|
||||
"Order": 6,
|
||||
"Order": 4,
|
||||
"Url": "/admin/developerkit/components",
|
||||
"Icon": "FcBiohazard",
|
||||
"RequiredPermissionName": "App.DeveloperKit.Components",
|
||||
|
|
@ -1033,27 +978,17 @@
|
|||
"ParentCode": "App.DeveloperKit",
|
||||
"Code": "App.DeveloperKit.DynamicServices",
|
||||
"DisplayName": "App.DeveloperKit.DynamicServices",
|
||||
"Order": 7,
|
||||
"Order": 5,
|
||||
"Url": "/admin/developerkit/dynamic-services",
|
||||
"Icon": "FcCommandLine",
|
||||
"RequiredPermissionName": "App.DeveloperKit.DynamicServices",
|
||||
"IsDisabled": false
|
||||
},
|
||||
{
|
||||
"ParentCode": "App.DeveloperKit",
|
||||
"Code": "App.SqlQueryManager",
|
||||
"DisplayName": "App.SqlQueryManager",
|
||||
"Order": 8,
|
||||
"Url": "/admin/sqlQueryManager",
|
||||
"Icon": "FaDatabase",
|
||||
"RequiredPermissionName": "App.SqlQueryManager",
|
||||
"IsDisabled": false
|
||||
},
|
||||
{
|
||||
"ParentCode": "App.DeveloperKit",
|
||||
"Code": "App.Listforms.Wizard",
|
||||
"DisplayName": "App.Listforms.Wizard",
|
||||
"Order": 9,
|
||||
"Order": 6,
|
||||
"Url": "/admin/listform/wizard",
|
||||
"Icon": "FcFlashAuto",
|
||||
"RequiredPermissionName": "App.Listforms.Wizard",
|
||||
|
|
|
|||
|
|
@ -2233,24 +2233,6 @@
|
|||
"MultiTenancySide": 3,
|
||||
"MenuGroup": "Erp|Kurs"
|
||||
},
|
||||
{
|
||||
"GroupName": "App.Administration",
|
||||
"Name": "App.DeveloperKit.Entity",
|
||||
"ParentName": "App.DeveloperKit",
|
||||
"DisplayName": "App.DeveloperKit.Entity",
|
||||
"IsEnabled": true,
|
||||
"MultiTenancySide": 3,
|
||||
"MenuGroup": "Erp|Kurs"
|
||||
},
|
||||
{
|
||||
"GroupName": "App.Administration",
|
||||
"Name": "App.DeveloperKit.Migrations",
|
||||
"ParentName": "App.DeveloperKit",
|
||||
"DisplayName": "App.DeveloperKit.Migrations",
|
||||
"IsEnabled": true,
|
||||
"MultiTenancySide": 3,
|
||||
"MenuGroup": "Erp|Kurs"
|
||||
},
|
||||
{
|
||||
"GroupName": "App.Administration",
|
||||
"Name": "App.DeveloperKit.CrudEndpoints",
|
||||
|
|
|
|||
|
|
@ -29,10 +29,13 @@ public enum TableNameEnum
|
|||
ForumCategory,
|
||||
ForumTopic,
|
||||
ForumPost,
|
||||
SqlTable,
|
||||
SqlTableField,
|
||||
SqlView,
|
||||
SqlStoredProcedure,
|
||||
SqlFunction,
|
||||
SqlQuery,
|
||||
CrudEndpoint,
|
||||
CrudMigration,
|
||||
CustomEntity,
|
||||
CustomEntityField,
|
||||
CustomEndpoint,
|
||||
CustomComponent,
|
||||
DynamicService,
|
||||
|
|
|
|||
|
|
@ -50,9 +50,8 @@ public static class TableNameResolver
|
|||
{ nameof(TableNameEnum.Branch), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||
{ nameof(TableNameEnum.BranchUsers), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||
{ nameof(TableNameEnum.GlobalSearch), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||
{ nameof(TableNameEnum.CustomEntity), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||
{ nameof(TableNameEnum.CustomEntityField), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||
{ nameof(TableNameEnum.CrudMigration), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||
{ nameof(TableNameEnum.SqlTable), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||
{ nameof(TableNameEnum.SqlTableField), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||
{ nameof(TableNameEnum.CrudEndpoint), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||
{ nameof(TableNameEnum.CustomEndpoint), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||
{ nameof(TableNameEnum.CustomComponent), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Sozsoft.Platform.Entities;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace Sozsoft.Platform.Domain.DeveloperKit;
|
||||
|
||||
public interface IApiMigrationRepository : IRepository<CrudMigration, Guid>
|
||||
{
|
||||
Task ExecuteSqlAsync(string sql);
|
||||
}
|
||||
|
||||
|
|
@ -15,9 +15,9 @@ public class CrudEndpoint : FullAuditedEntity<Guid>, IMultiTenant
|
|||
public string CsharpCode { get; set; } = string.Empty;
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
// Foreign key to CustomEntity
|
||||
// Foreign key to SqlTable
|
||||
public Guid EntityId { get; set; }
|
||||
public virtual CustomEntity Entity { get; set; } = null!;
|
||||
public virtual SqlTable Entity { get; set; } = null!;
|
||||
public CrudEndpoint()
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
using System;
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
|
||||
namespace Sozsoft.Platform.Entities;
|
||||
|
||||
public class CrudMigration : AuditedEntity<Guid>, IMultiTenant
|
||||
{
|
||||
public virtual Guid? TenantId { get; protected set; }
|
||||
|
||||
public Guid EntityId { get; set; }
|
||||
public string EntityName { get; set; } = string.Empty;
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public string SqlScript { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = "Askıda"; // "pending" | "applied" | "failed"
|
||||
public DateTime? AppliedAt { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
public virtual CustomEntity Entity { get; set; } = null!;
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ using Volo.Abp.MultiTenancy;
|
|||
|
||||
namespace Sozsoft.Platform.Entities;
|
||||
|
||||
public class CustomEntity : FullAuditedEntity<Guid>, IMultiTenant
|
||||
public class SqlTable : FullAuditedEntity<Guid>, IMultiTenant
|
||||
{
|
||||
public virtual Guid? TenantId { get; protected set; }
|
||||
|
||||
|
|
@ -17,19 +17,17 @@ public class CustomEntity : FullAuditedEntity<Guid>, IMultiTenant
|
|||
public bool IsActive { get; set; } = true;
|
||||
public bool IsFullAuditedEntity { get; set; } = true;
|
||||
public bool IsMultiTenant { get; set; } = false;
|
||||
public string MigrationStatus { get; set; } = "Askıda";
|
||||
public Guid? MigrationId { get; set; }
|
||||
public string EndpointStatus { get; set; } = "Askıda"; // "pending" | "applied" | "failed"
|
||||
public string EndpointStatus { get; set; } = "pending"; // "pending" | "applied" | "failed"
|
||||
|
||||
public virtual ICollection<CustomEntityField> Fields { get; set; } = [];
|
||||
public virtual ICollection<SqlTableField> Fields { get; set; } = [];
|
||||
|
||||
public CustomEntity()
|
||||
public SqlTable()
|
||||
{
|
||||
Id = Guid.NewGuid(); // Burada erişim mümkün çünkü sınıfın içi
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomEntityField : FullAuditedEntity<Guid>
|
||||
public class SqlTableField : FullAuditedEntity<Guid>
|
||||
{
|
||||
public Guid EntityId { get; set; } = Guid.NewGuid();
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
|
@ -41,9 +39,9 @@ public class CustomEntityField : FullAuditedEntity<Guid>
|
|||
public string? Description { get; set; }
|
||||
public int DisplayOrder { get; set; } = 0;
|
||||
|
||||
public virtual CustomEntity Entity { get; set; } = null!;
|
||||
public virtual SqlTable Entity { get; set; } = null!;
|
||||
|
||||
public CustomEntityField()
|
||||
public SqlTableField()
|
||||
{
|
||||
Id = Guid.NewGuid(); // Burada erişim mümkün çünkü sınıfın içi
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Sozsoft.Platform.Entities;
|
||||
using Sozsoft.Platform.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
|
||||
using Volo.Abp.EntityFrameworkCore;
|
||||
|
||||
namespace Sozsoft.Platform.Domain.DeveloperKit;
|
||||
|
||||
public class PlatformApiMigrationRepository : EfCoreRepository<PlatformDbContext, CrudMigration, Guid>, IApiMigrationRepository
|
||||
{
|
||||
public PlatformApiMigrationRepository(IDbContextProvider<PlatformDbContext> dbContextProvider)
|
||||
: base(dbContextProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task ExecuteSqlAsync(string sql)
|
||||
{
|
||||
var dbContext = await GetDbContextAsync();
|
||||
await dbContext.Database.ExecuteSqlRawAsync(sql);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -15,14 +15,14 @@ using Volo.Abp.EntityFrameworkCore;
|
|||
public class DynamicEntityManager : IDynamicEntityManager
|
||||
{
|
||||
private readonly IDbContextProvider<PlatformDbContext> _dbContextProvider;
|
||||
private readonly IRepository<CustomEntity, Guid> _customEntityRepository;
|
||||
private readonly IRepository<SqlTable, Guid> _sqlTableRepository;
|
||||
|
||||
public DynamicEntityManager(
|
||||
IDbContextProvider<PlatformDbContext> dbContextProvider,
|
||||
IRepository<CustomEntity, Guid> customEntityRepository)
|
||||
IRepository<SqlTable, Guid> sqlTableRepository)
|
||||
{
|
||||
_dbContextProvider = dbContextProvider;
|
||||
_customEntityRepository = customEntityRepository;
|
||||
_sqlTableRepository = sqlTableRepository;
|
||||
}
|
||||
|
||||
public async Task<List<object>?> GetEntityListAsync(string entityName)
|
||||
|
|
@ -167,9 +167,9 @@ public class DynamicEntityManager : IDynamicEntityManager
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<CustomEntity> GetEntityDefinitionAsync(string entityName)
|
||||
private async Task<SqlTable> GetEntityDefinitionAsync(string entityName)
|
||||
{
|
||||
var queryable = await _customEntityRepository.WithDetailsAsync(x => x.Fields);
|
||||
var queryable = await _sqlTableRepository.WithDetailsAsync(x => x.Fields);
|
||||
var entity = await queryable.FirstOrDefaultAsync(x => x.Name.ToLower() == entityName.ToLower());
|
||||
|
||||
return entity ?? throw new UserFriendlyException($"Entity '{entityName}' not found.");
|
||||
|
|
|
|||
|
|
@ -58,9 +58,8 @@ public class PlatformDbContext :
|
|||
public DbSet<ForumCategory> ForumCategories { get; set; }
|
||||
public DbSet<ForumTopic> ForumTopics { get; set; }
|
||||
public DbSet<ForumPost> ForumPosts { get; set; }
|
||||
public DbSet<CustomEntity> CustomEntities { get; set; }
|
||||
public DbSet<CustomEntityField> EntityFields { get; set; }
|
||||
public DbSet<CrudMigration> Migrations { get; set; }
|
||||
public DbSet<SqlTable> CustomEntities { get; set; }
|
||||
public DbSet<SqlTableField> EntityFields { get; set; }
|
||||
public DbSet<CrudEndpoint> GeneratedEndpoints { get; set; }
|
||||
public DbSet<CustomEndpoint> CustomEndpoints { get; set; }
|
||||
public DbSet<CustomComponent> CustomComponents { get; set; }
|
||||
|
|
@ -516,9 +515,9 @@ public class PlatformDbContext :
|
|||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
builder.Entity<CustomEntity>(b =>
|
||||
builder.Entity<SqlTable>(b =>
|
||||
{
|
||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.CustomEntity)), Prefix.DbSchema);
|
||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SqlTable)), Prefix.DbSchema);
|
||||
b.ConfigureByConvention();
|
||||
|
||||
b.HasKey(x => x.Id);
|
||||
|
|
@ -526,7 +525,6 @@ public class PlatformDbContext :
|
|||
b.Property(x => x.DisplayName).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.TableName).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.Description).HasMaxLength(512);
|
||||
b.Property(x => x.MigrationStatus).IsRequired().HasMaxLength(64);
|
||||
b.Property(x => x.EndpointStatus).IsRequired().HasMaxLength(64);
|
||||
b.Property(x => x.Menu).IsRequired().HasMaxLength(128);
|
||||
|
||||
|
|
@ -536,9 +534,9 @@ public class PlatformDbContext :
|
|||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
builder.Entity<CustomEntityField>(b =>
|
||||
builder.Entity<SqlTableField>(b =>
|
||||
{
|
||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.CustomEntityField)), Prefix.DbSchema);
|
||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SqlTableField)), Prefix.DbSchema);
|
||||
b.ConfigureByConvention();
|
||||
|
||||
b.HasKey(x => x.Id);
|
||||
|
|
@ -548,24 +546,6 @@ public class PlatformDbContext :
|
|||
b.Property(x => x.DefaultValue).HasMaxLength(256);
|
||||
});
|
||||
|
||||
builder.Entity<CrudMigration>(b =>
|
||||
{
|
||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.CrudMigration)), Prefix.DbSchema);
|
||||
b.ConfigureByConvention();
|
||||
|
||||
b.HasKey(x => x.Id);
|
||||
b.Property(x => x.EntityName).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.FileName).IsRequired().HasMaxLength(256);
|
||||
b.Property(x => x.SqlScript).IsRequired();
|
||||
b.Property(x => x.Status).IsRequired().HasMaxLength(64);
|
||||
b.Property(x => x.ErrorMessage).HasMaxLength(1024);
|
||||
|
||||
b.HasOne(x => x.Entity)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.EntityId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
builder.Entity<CrudEndpoint>(b =>
|
||||
{
|
||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.CrudEndpoint)), Prefix.DbSchema);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
|||
namespace Sozsoft.Platform.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlatformDbContext))]
|
||||
[Migration("20260301173343_Initial")]
|
||||
[Migration("20260301203437_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -1419,67 +1419,6 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.ToTable("Sas_T_CrudEndpoint", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CrudMigration", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("AppliedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid>("EntityId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("EntityName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<string>("SqlScript")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EntityId");
|
||||
|
||||
b.ToTable("Sas_T_CrudMigration", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Currency", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
|
|
@ -1688,171 +1627,6 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.ToTable("Sas_T_CustomEndpoint", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CustomEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("EndpointStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<bool>("IsFullAuditedEntity")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsMultiTenant")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<string>("Menu")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<Guid?>("MigrationId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("MigrationStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("TableName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Sas_T_CustomEntity", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CustomEntityField", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<string>("DefaultValue")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<int>("DisplayOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("EntityId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<bool>("IsRequired")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsUnique")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<int?>("MaxLength")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EntityId");
|
||||
|
||||
b.ToTable("Sas_T_CustomEntityField", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.DataSource", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
|
@ -4001,6 +3775,163 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.ToTable("Adm_T_SkillType", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.SqlTable", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("EndpointStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<bool>("IsFullAuditedEntity")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsMultiTenant")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<string>("Menu")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("TableName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Sas_T_SqlTable", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.SqlTableField", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<string>("DefaultValue")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<int>("DisplayOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("EntityId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<bool>("IsRequired")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsUnique")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<int?>("MaxLength")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EntityId");
|
||||
|
||||
b.ToTable("Sas_T_SqlTableField", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Uom", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
|
|
@ -4663,7 +4594,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
|
||||
b.HasIndex("SchemaName", "FunctionName");
|
||||
|
||||
b.ToTable("Sqm_T_SqlFunction", (string)null);
|
||||
b.ToTable("Sas_T_SqlFunction", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.SqlQueryManager.Domain.Entities.SqlQuery", b =>
|
||||
|
|
@ -4763,7 +4694,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("Sqm_T_SqlQuery", (string)null);
|
||||
b.ToTable("Sas_T_SqlQuery", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.SqlQueryManager.Domain.Entities.SqlStoredProcedure", b =>
|
||||
|
|
@ -4861,7 +4792,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
|
||||
b.HasIndex("SchemaName", "ProcedureName");
|
||||
|
||||
b.ToTable("Sqm_T_SqlStoredProcedure", (string)null);
|
||||
b.ToTable("Sas_T_SqlStoredProcedure", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.SqlQueryManager.Domain.Entities.SqlView", b =>
|
||||
|
|
@ -4958,7 +4889,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
|
||||
b.HasIndex("SchemaName", "ViewName");
|
||||
|
||||
b.ToTable("Sqm_T_SqlView", (string)null);
|
||||
b.ToTable("Sas_T_SqlView", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b =>
|
||||
|
|
@ -6879,7 +6810,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CrudEndpoint", b =>
|
||||
{
|
||||
b.HasOne("Sozsoft.Platform.Entities.CustomEntity", "Entity")
|
||||
b.HasOne("Sozsoft.Platform.Entities.SqlTable", "Entity")
|
||||
.WithMany()
|
||||
.HasForeignKey("EntityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
|
|
@ -6888,28 +6819,6 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Navigation("Entity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CrudMigration", b =>
|
||||
{
|
||||
b.HasOne("Sozsoft.Platform.Entities.CustomEntity", "Entity")
|
||||
.WithMany()
|
||||
.HasForeignKey("EntityId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CustomEntityField", b =>
|
||||
{
|
||||
b.HasOne("Sozsoft.Platform.Entities.CustomEntity", "Entity")
|
||||
.WithMany("Fields")
|
||||
.HasForeignKey("EntityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.District", b =>
|
||||
{
|
||||
b.HasOne("Sozsoft.Platform.Entities.City", null)
|
||||
|
|
@ -7000,6 +6909,17 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Navigation("SkillType");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.SqlTableField", b =>
|
||||
{
|
||||
b.HasOne("Sozsoft.Platform.Entities.SqlTable", "Entity")
|
||||
.WithMany("Fields")
|
||||
.HasForeignKey("EntityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Uom", b =>
|
||||
{
|
||||
b.HasOne("Sozsoft.Platform.Entities.UomCategory", "UomCategory")
|
||||
|
|
@ -7222,11 +7142,6 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Navigation("Cities");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CustomEntity", b =>
|
||||
{
|
||||
b.Navigation("Fields");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
|
|
@ -7244,6 +7159,11 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Navigation("Skills");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.SqlTable", b =>
|
||||
{
|
||||
b.Navigation("Fields");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.UomCategory", b =>
|
||||
{
|
||||
b.Navigation("Uoms");
|
||||
|
|
@ -1362,36 +1362,6 @@ namespace Sozsoft.Platform.Migrations
|
|||
table.PrimaryKey("PK_Sas_T_CustomEndpoint", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sas_T_CustomEntity",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
Menu = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
DisplayName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
TableName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Description = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||
IsFullAuditedEntity = table.Column<bool>(type: "bit", nullable: false),
|
||||
IsMultiTenant = table.Column<bool>(type: "bit", nullable: false),
|
||||
MigrationStatus = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
MigrationId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
EndpointStatus = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sas_T_CustomEntity", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sas_T_DynamicService",
|
||||
columns: table => new
|
||||
|
|
@ -1531,7 +1501,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sqm_T_SqlFunction",
|
||||
name: "Sas_T_SqlFunction",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
|
|
@ -1559,11 +1529,11 @@ namespace Sozsoft.Platform.Migrations
|
|||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sqm_T_SqlFunction", x => x.Id);
|
||||
table.PrimaryKey("PK_Sas_T_SqlFunction", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sqm_T_SqlQuery",
|
||||
name: "Sas_T_SqlQuery",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
|
|
@ -1590,11 +1560,11 @@ namespace Sozsoft.Platform.Migrations
|
|||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sqm_T_SqlQuery", x => x.Id);
|
||||
table.PrimaryKey("PK_Sas_T_SqlQuery", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sqm_T_SqlStoredProcedure",
|
||||
name: "Sas_T_SqlStoredProcedure",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
|
|
@ -1620,11 +1590,39 @@ namespace Sozsoft.Platform.Migrations
|
|||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sqm_T_SqlStoredProcedure", x => x.Id);
|
||||
table.PrimaryKey("PK_Sas_T_SqlStoredProcedure", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sqm_T_SqlView",
|
||||
name: "Sas_T_SqlTable",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
Menu = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
DisplayName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
TableName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Description = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||
IsFullAuditedEntity = table.Column<bool>(type: "bit", nullable: false),
|
||||
IsMultiTenant = table.Column<bool>(type: "bit", nullable: false),
|
||||
EndpointStatus = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sas_T_SqlTable", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sas_T_SqlView",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
|
|
@ -1650,7 +1648,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sqm_T_SqlView", x => x.Id);
|
||||
table.PrimaryKey("PK_Sas_T_SqlView", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
|
|
@ -2376,100 +2374,6 @@ namespace Sozsoft.Platform.Migrations
|
|||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sas_T_CrudEndpoint",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
EntityName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Method = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||
Path = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
OperationType = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
CsharpCode = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||
EntityId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sas_T_CrudEndpoint", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Sas_T_CrudEndpoint_Sas_T_CustomEntity_EntityId",
|
||||
column: x => x.EntityId,
|
||||
principalTable: "Sas_T_CustomEntity",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sas_T_CrudMigration",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
EntityId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
EntityName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
FileName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
SqlScript = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Status = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
AppliedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
ErrorMessage = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sas_T_CrudMigration", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Sas_T_CrudMigration_Sas_T_CustomEntity_EntityId",
|
||||
column: x => x.EntityId,
|
||||
principalTable: "Sas_T_CustomEntity",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sas_T_CustomEntityField",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
EntityId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Type = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
IsRequired = table.Column<bool>(type: "bit", nullable: false),
|
||||
MaxLength = table.Column<int>(type: "int", nullable: true),
|
||||
IsUnique = table.Column<bool>(type: "bit", nullable: false),
|
||||
DefaultValue = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
Description = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||
DisplayOrder = table.Column<int>(type: "int", nullable: false),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sas_T_CustomEntityField", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Sas_T_CustomEntityField_Sas_T_CustomEntity_EntityId",
|
||||
column: x => x.EntityId,
|
||||
principalTable: "Sas_T_CustomEntity",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sas_T_ReportTemplate",
|
||||
columns: table => new
|
||||
|
|
@ -2500,6 +2404,71 @@ namespace Sozsoft.Platform.Migrations
|
|||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sas_T_CrudEndpoint",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
EntityName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Method = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||
Path = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
OperationType = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
CsharpCode = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||
EntityId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sas_T_CrudEndpoint", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Sas_T_CrudEndpoint_Sas_T_SqlTable_EntityId",
|
||||
column: x => x.EntityId,
|
||||
principalTable: "Sas_T_SqlTable",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Sas_T_SqlTableField",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
EntityId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Type = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
IsRequired = table.Column<bool>(type: "bit", nullable: false),
|
||||
MaxLength = table.Column<int>(type: "int", nullable: true),
|
||||
IsUnique = table.Column<bool>(type: "bit", nullable: false),
|
||||
DefaultValue = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
Description = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||
DisplayOrder = table.Column<int>(type: "int", nullable: false),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Sas_T_SqlTableField", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Sas_T_SqlTableField_Sas_T_SqlTable_EntityId",
|
||||
column: x => x.EntityId,
|
||||
principalTable: "Sas_T_SqlTable",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AbpEntityPropertyChanges",
|
||||
columns: table => new
|
||||
|
|
@ -3148,104 +3117,99 @@ namespace Sozsoft.Platform.Migrations
|
|||
table: "Sas_T_CrudEndpoint",
|
||||
column: "EntityId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sas_T_CrudMigration_EntityId",
|
||||
table: "Sas_T_CrudMigration",
|
||||
column: "EntityId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sas_T_CustomEntityField_EntityId",
|
||||
table: "Sas_T_CustomEntityField",
|
||||
column: "EntityId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sas_T_ReportTemplate_CategoryId",
|
||||
table: "Sas_T_ReportTemplate",
|
||||
column: "CategoryId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlFunction_DataSourceCode",
|
||||
table: "Sqm_T_SqlFunction",
|
||||
name: "IX_Sas_T_SqlFunction_DataSourceCode",
|
||||
table: "Sas_T_SqlFunction",
|
||||
column: "DataSourceCode");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlFunction_FunctionType",
|
||||
table: "Sqm_T_SqlFunction",
|
||||
name: "IX_Sas_T_SqlFunction_FunctionType",
|
||||
table: "Sas_T_SqlFunction",
|
||||
column: "FunctionType");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlFunction_IsDeployed",
|
||||
table: "Sqm_T_SqlFunction",
|
||||
name: "IX_Sas_T_SqlFunction_IsDeployed",
|
||||
table: "Sas_T_SqlFunction",
|
||||
column: "IsDeployed");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlFunction_SchemaName_FunctionName",
|
||||
table: "Sqm_T_SqlFunction",
|
||||
name: "IX_Sas_T_SqlFunction_SchemaName_FunctionName",
|
||||
table: "Sas_T_SqlFunction",
|
||||
columns: new[] { "SchemaName", "FunctionName" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlFunction_Status",
|
||||
table: "Sqm_T_SqlFunction",
|
||||
name: "IX_Sas_T_SqlFunction_Status",
|
||||
table: "Sas_T_SqlFunction",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlQuery_Category",
|
||||
table: "Sqm_T_SqlQuery",
|
||||
name: "IX_Sas_T_SqlQuery_Category",
|
||||
table: "Sas_T_SqlQuery",
|
||||
column: "Category");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlQuery_Code",
|
||||
table: "Sqm_T_SqlQuery",
|
||||
name: "IX_Sas_T_SqlQuery_Code",
|
||||
table: "Sas_T_SqlQuery",
|
||||
column: "Code");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlQuery_DataSourceCode",
|
||||
table: "Sqm_T_SqlQuery",
|
||||
name: "IX_Sas_T_SqlQuery_DataSourceCode",
|
||||
table: "Sas_T_SqlQuery",
|
||||
column: "DataSourceCode");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlQuery_Status",
|
||||
table: "Sqm_T_SqlQuery",
|
||||
name: "IX_Sas_T_SqlQuery_Status",
|
||||
table: "Sas_T_SqlQuery",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlStoredProcedure_DataSourceCode",
|
||||
table: "Sqm_T_SqlStoredProcedure",
|
||||
name: "IX_Sas_T_SqlStoredProcedure_DataSourceCode",
|
||||
table: "Sas_T_SqlStoredProcedure",
|
||||
column: "DataSourceCode");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlStoredProcedure_IsDeployed",
|
||||
table: "Sqm_T_SqlStoredProcedure",
|
||||
name: "IX_Sas_T_SqlStoredProcedure_IsDeployed",
|
||||
table: "Sas_T_SqlStoredProcedure",
|
||||
column: "IsDeployed");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlStoredProcedure_SchemaName_ProcedureName",
|
||||
table: "Sqm_T_SqlStoredProcedure",
|
||||
name: "IX_Sas_T_SqlStoredProcedure_SchemaName_ProcedureName",
|
||||
table: "Sas_T_SqlStoredProcedure",
|
||||
columns: new[] { "SchemaName", "ProcedureName" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlStoredProcedure_Status",
|
||||
table: "Sqm_T_SqlStoredProcedure",
|
||||
name: "IX_Sas_T_SqlStoredProcedure_Status",
|
||||
table: "Sas_T_SqlStoredProcedure",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlView_DataSourceCode",
|
||||
table: "Sqm_T_SqlView",
|
||||
name: "IX_Sas_T_SqlTableField_EntityId",
|
||||
table: "Sas_T_SqlTableField",
|
||||
column: "EntityId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sas_T_SqlView_DataSourceCode",
|
||||
table: "Sas_T_SqlView",
|
||||
column: "DataSourceCode");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlView_IsDeployed",
|
||||
table: "Sqm_T_SqlView",
|
||||
name: "IX_Sas_T_SqlView_IsDeployed",
|
||||
table: "Sas_T_SqlView",
|
||||
column: "IsDeployed");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlView_SchemaName_ViewName",
|
||||
table: "Sqm_T_SqlView",
|
||||
name: "IX_Sas_T_SqlView_SchemaName_ViewName",
|
||||
table: "Sas_T_SqlView",
|
||||
columns: new[] { "SchemaName", "ViewName" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Sqm_T_SqlView_Status",
|
||||
table: "Sqm_T_SqlView",
|
||||
name: "IX_Sas_T_SqlView_Status",
|
||||
table: "Sas_T_SqlView",
|
||||
column: "Status");
|
||||
}
|
||||
|
||||
|
|
@ -3426,18 +3390,12 @@ namespace Sozsoft.Platform.Migrations
|
|||
migrationBuilder.DropTable(
|
||||
name: "Sas_T_CrudEndpoint");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sas_T_CrudMigration");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sas_T_CustomComponent");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sas_T_CustomEndpoint");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sas_T_CustomEntityField");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sas_T_DynamicService");
|
||||
|
||||
|
|
@ -3457,16 +3415,19 @@ namespace Sozsoft.Platform.Migrations
|
|||
name: "Sas_T_Sector");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sqm_T_SqlFunction");
|
||||
name: "Sas_T_SqlFunction");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sqm_T_SqlQuery");
|
||||
name: "Sas_T_SqlQuery");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sqm_T_SqlStoredProcedure");
|
||||
name: "Sas_T_SqlStoredProcedure");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sqm_T_SqlView");
|
||||
name: "Sas_T_SqlTableField");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sas_T_SqlView");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AbpEntityChanges");
|
||||
|
|
@ -3523,10 +3484,10 @@ namespace Sozsoft.Platform.Migrations
|
|||
name: "Sas_T_Branch");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sas_T_CustomEntity");
|
||||
name: "Sas_T_ReportCategory");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sas_T_ReportCategory");
|
||||
name: "Sas_T_SqlTable");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AbpAuditLogs");
|
||||
|
|
@ -1416,67 +1416,6 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.ToTable("Sas_T_CrudEndpoint", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CrudMigration", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("AppliedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid>("EntityId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("EntityName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<string>("SqlScript")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EntityId");
|
||||
|
||||
b.ToTable("Sas_T_CrudMigration", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Currency", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
|
|
@ -1685,171 +1624,6 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.ToTable("Sas_T_CustomEndpoint", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CustomEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("EndpointStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<bool>("IsFullAuditedEntity")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsMultiTenant")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<string>("Menu")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<Guid?>("MigrationId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("MigrationStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("TableName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Sas_T_CustomEntity", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CustomEntityField", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<string>("DefaultValue")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<int>("DisplayOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("EntityId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<bool>("IsRequired")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsUnique")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<int?>("MaxLength")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EntityId");
|
||||
|
||||
b.ToTable("Sas_T_CustomEntityField", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.DataSource", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
|
@ -3998,6 +3772,163 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.ToTable("Adm_T_SkillType", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.SqlTable", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("EndpointStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<bool>("IsFullAuditedEntity")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsMultiTenant")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<string>("Menu")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("TableName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Sas_T_SqlTable", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.SqlTableField", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<string>("DefaultValue")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<int>("DisplayOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("EntityId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<bool>("IsRequired")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsUnique")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<int?>("MaxLength")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EntityId");
|
||||
|
||||
b.ToTable("Sas_T_SqlTableField", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Uom", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
|
|
@ -4660,7 +4591,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
|
||||
b.HasIndex("SchemaName", "FunctionName");
|
||||
|
||||
b.ToTable("Sqm_T_SqlFunction", (string)null);
|
||||
b.ToTable("Sas_T_SqlFunction", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.SqlQueryManager.Domain.Entities.SqlQuery", b =>
|
||||
|
|
@ -4760,7 +4691,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("Sqm_T_SqlQuery", (string)null);
|
||||
b.ToTable("Sas_T_SqlQuery", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.SqlQueryManager.Domain.Entities.SqlStoredProcedure", b =>
|
||||
|
|
@ -4858,7 +4789,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
|
||||
b.HasIndex("SchemaName", "ProcedureName");
|
||||
|
||||
b.ToTable("Sqm_T_SqlStoredProcedure", (string)null);
|
||||
b.ToTable("Sas_T_SqlStoredProcedure", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.SqlQueryManager.Domain.Entities.SqlView", b =>
|
||||
|
|
@ -4955,7 +4886,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
|
||||
b.HasIndex("SchemaName", "ViewName");
|
||||
|
||||
b.ToTable("Sqm_T_SqlView", (string)null);
|
||||
b.ToTable("Sas_T_SqlView", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b =>
|
||||
|
|
@ -6876,7 +6807,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CrudEndpoint", b =>
|
||||
{
|
||||
b.HasOne("Sozsoft.Platform.Entities.CustomEntity", "Entity")
|
||||
b.HasOne("Sozsoft.Platform.Entities.SqlTable", "Entity")
|
||||
.WithMany()
|
||||
.HasForeignKey("EntityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
|
|
@ -6885,28 +6816,6 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Navigation("Entity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CrudMigration", b =>
|
||||
{
|
||||
b.HasOne("Sozsoft.Platform.Entities.CustomEntity", "Entity")
|
||||
.WithMany()
|
||||
.HasForeignKey("EntityId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CustomEntityField", b =>
|
||||
{
|
||||
b.HasOne("Sozsoft.Platform.Entities.CustomEntity", "Entity")
|
||||
.WithMany("Fields")
|
||||
.HasForeignKey("EntityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.District", b =>
|
||||
{
|
||||
b.HasOne("Sozsoft.Platform.Entities.City", null)
|
||||
|
|
@ -6997,6 +6906,17 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Navigation("SkillType");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.SqlTableField", b =>
|
||||
{
|
||||
b.HasOne("Sozsoft.Platform.Entities.SqlTable", "Entity")
|
||||
.WithMany("Fields")
|
||||
.HasForeignKey("EntityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Entity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Uom", b =>
|
||||
{
|
||||
b.HasOne("Sozsoft.Platform.Entities.UomCategory", "UomCategory")
|
||||
|
|
@ -7219,11 +7139,6 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Navigation("Cities");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CustomEntity", b =>
|
||||
{
|
||||
b.Navigation("Fields");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
|
|
@ -7241,6 +7156,11 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Navigation("Skills");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.SqlTable", b =>
|
||||
{
|
||||
b.Navigation("Fields");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Sozsoft.Platform.Entities.UomCategory", b =>
|
||||
{
|
||||
b.Navigation("Uoms");
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@
|
|||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "ProductListComponent",
|
||||
"code": "const ProductListComponent = ({\n title = \"Product\"\n}) => {\n return (\n <DynamicEntityComponent id=\"c_mdljvvmq_fno52v\" title={title} />\n );\n};\n\nexport default ProductListComponent;",
|
||||
"name": "RoleListComponent",
|
||||
"code": "const RoleListComponent = ({\n title = \"AbpRoles\"\n}) => {\n return (\n <DynamicEntityComponent id=\"c_mdljvvmq_fno52v\" title={title} />\n );\n};\n\nexport default RoleListComponent;",
|
||||
"props": null,
|
||||
"description": null,
|
||||
"isActive": true,
|
||||
|
|
|
|||
|
|
@ -1,790 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
import { useEntities } from '../../contexts/EntityContext'
|
||||
import axios from 'axios'
|
||||
import {
|
||||
FaBook,
|
||||
FaSearch,
|
||||
FaFilter,
|
||||
FaGlobe,
|
||||
FaCopy,
|
||||
FaCheckCircle,
|
||||
FaExclamationCircle,
|
||||
FaDatabase,
|
||||
FaSyncAlt,
|
||||
FaPaperPlane,
|
||||
FaPlusCircle,
|
||||
FaEdit,
|
||||
FaTrash,
|
||||
} from 'react-icons/fa'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
interface EndpointType {
|
||||
id: string
|
||||
name: string
|
||||
method: string
|
||||
path: string
|
||||
description?: string
|
||||
type: 'generated'
|
||||
operationType?: string
|
||||
entityName?: string
|
||||
}
|
||||
|
||||
interface TestResult {
|
||||
success: boolean
|
||||
status: number
|
||||
data?: unknown
|
||||
error?: unknown
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
interface ParameterInput {
|
||||
name: string
|
||||
value: string
|
||||
type: 'path' | 'query' | 'body'
|
||||
required: boolean
|
||||
description?: string
|
||||
}
|
||||
|
||||
const CrudEndpointManager: React.FC = () => {
|
||||
const { generatedEndpoints } = useEntities()
|
||||
const { translate } = useLocalization()
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [filterMethod, setFilterMethod] = useState<'all' | 'GET' | 'POST' | 'PUT' | 'DELETE'>('all')
|
||||
const [selectedEndpoint, setSelectedEndpoint] = useState<string | null>(null)
|
||||
const [testResults, setTestResults] = useState<Record<string, TestResult>>({})
|
||||
const [loadingEndpoints, setLoadingEndpoints] = useState<Set<string>>(new Set())
|
||||
const [parameterValues, setParameterValues] = useState<Record<string, Record<string, string>>>({})
|
||||
const [requestBodies, setRequestBodies] = useState<Record<string, string>>({})
|
||||
|
||||
// Only show generated CRUD endpoints
|
||||
const allEndpoints: EndpointType[] = [
|
||||
...generatedEndpoints
|
||||
.filter((e) => e.isActive)
|
||||
.map((e) => ({
|
||||
id: e.id,
|
||||
name: `${e.entityName} ${e.operationType}`,
|
||||
method: e.method,
|
||||
path: e.path,
|
||||
description: `${e.operationType} operation for ${e.entityName} entity`,
|
||||
type: 'generated' as const,
|
||||
operationType: e.operationType,
|
||||
entityName: e.entityName,
|
||||
})),
|
||||
]
|
||||
|
||||
const filteredEndpoints = allEndpoints.filter((endpoint) => {
|
||||
const matchesSearch =
|
||||
endpoint.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
endpoint.path.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(endpoint.description || '').toLowerCase().includes(searchTerm.toLowerCase())
|
||||
|
||||
const matchesMethodFilter = filterMethod === 'all' || endpoint.method === filterMethod
|
||||
|
||||
return matchesSearch && matchesMethodFilter
|
||||
})
|
||||
|
||||
const getMethodColor = (method: string) => {
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
return 'bg-blue-100 text-blue-800 border-blue-200'
|
||||
case 'POST':
|
||||
return 'bg-green-100 text-green-800 border-green-200'
|
||||
case 'PUT':
|
||||
return 'bg-yellow-100 text-yellow-800 border-yellow-200'
|
||||
case 'DELETE':
|
||||
return 'bg-red-100 text-red-800 border-red-200'
|
||||
default:
|
||||
return 'bg-slate-100 text-slate-800 border-slate-200'
|
||||
}
|
||||
}
|
||||
|
||||
const getResponseExample = (endpoint: EndpointType) => {
|
||||
switch (endpoint.operationType) {
|
||||
case 'GetList':
|
||||
case 'GetPaged':
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
||||
name: 'Sample Item',
|
||||
creationTime: '2024-01-15T10:30:00Z',
|
||||
},
|
||||
],
|
||||
totalCount: 1,
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
}
|
||||
case 'GetById':
|
||||
return {
|
||||
id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
||||
name: 'Sample Item',
|
||||
creationTime: '2024-01-15T10:30:00Z',
|
||||
lastModificationTime: '2024-01-15T10:30:00Z',
|
||||
}
|
||||
case 'Create':
|
||||
case 'Update':
|
||||
return {
|
||||
id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
||||
name: 'Sample Item',
|
||||
creationTime: '2024-01-15T10:30:00Z',
|
||||
lastModificationTime: '2024-01-15T10:30:00Z',
|
||||
}
|
||||
case 'Delete':
|
||||
return {
|
||||
success: true,
|
||||
message: 'Item deleted successfully',
|
||||
}
|
||||
default:
|
||||
return {
|
||||
message: 'Hello from API!',
|
||||
timestamp: '2024-01-15T10:30:00Z',
|
||||
success: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getRequestExample = (endpoint: EndpointType) => {
|
||||
if (endpoint.operationType === 'Create' || endpoint.operationType === 'Update') {
|
||||
return {
|
||||
Name: 'New Item',
|
||||
Description: 'Item description',
|
||||
IsActive: true,
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text)
|
||||
}
|
||||
|
||||
// Get required parameters for each endpoint type
|
||||
const getEndpointParameters = (endpoint: EndpointType): ParameterInput[] => {
|
||||
const parameters: ParameterInput[] = []
|
||||
const currentValues = parameterValues[endpoint.id] || {}
|
||||
|
||||
switch (endpoint.operationType) {
|
||||
case 'GetById':
|
||||
case 'Update':
|
||||
case 'Delete':
|
||||
parameters.push({
|
||||
name: 'id',
|
||||
value: currentValues.id || '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
||||
type: 'path',
|
||||
required: true,
|
||||
description: 'Unique identifier for the entity',
|
||||
})
|
||||
break
|
||||
case 'GetList':
|
||||
case 'GetPaged':
|
||||
parameters.push(
|
||||
{
|
||||
name: 'SkipCount',
|
||||
value: currentValues.SkipCount || '0',
|
||||
type: 'query',
|
||||
required: false,
|
||||
description: 'Number of records to skip',
|
||||
},
|
||||
{
|
||||
name: 'MaxResultCount',
|
||||
value: currentValues.MaxResultCount || '10',
|
||||
type: 'query',
|
||||
required: false,
|
||||
description: 'Maximum number of records to return',
|
||||
},
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
return parameters
|
||||
}
|
||||
|
||||
// Check if endpoint needs request body
|
||||
const needsRequestBody = (endpoint: EndpointType): boolean => {
|
||||
return endpoint.operationType === 'Create' || endpoint.operationType === 'Update'
|
||||
}
|
||||
|
||||
// Update parameter value
|
||||
const updateParameterValue = (endpointId: string, paramName: string, value: string) => {
|
||||
setParameterValues((prev) => ({
|
||||
...prev,
|
||||
[endpointId]: {
|
||||
...prev[endpointId],
|
||||
[paramName]: value,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
// Update request body
|
||||
const updateRequestBody = (endpointId: string, body: string) => {
|
||||
setRequestBodies((prev) => ({
|
||||
...prev,
|
||||
[endpointId]: body,
|
||||
}))
|
||||
}
|
||||
|
||||
// Initialize default values for endpoint when first opened
|
||||
const initializeEndpointDefaults = (endpoint: EndpointType) => {
|
||||
if (!parameterValues[endpoint.id]) {
|
||||
const parameters = getEndpointParameters(endpoint)
|
||||
const defaultValues: Record<string, string> = {}
|
||||
|
||||
parameters.forEach((param) => {
|
||||
defaultValues[param.name] = param.value
|
||||
})
|
||||
|
||||
setParameterValues((prev) => ({
|
||||
...prev,
|
||||
[endpoint.id]: defaultValues,
|
||||
}))
|
||||
}
|
||||
|
||||
if (!requestBodies[endpoint.id] && needsRequestBody(endpoint)) {
|
||||
const example = getRequestExample(endpoint)
|
||||
if (example) {
|
||||
setRequestBodies((prev) => ({
|
||||
...prev,
|
||||
[endpoint.id]: JSON.stringify(example, null, 2),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get current request body for endpoint
|
||||
const getCurrentRequestBody = (endpoint: EndpointType): string => {
|
||||
const stored = requestBodies[endpoint.id]
|
||||
if (stored) return stored
|
||||
|
||||
const example = getRequestExample(endpoint)
|
||||
return example ? JSON.stringify(example, null, 2) : ''
|
||||
}
|
||||
|
||||
const testEndpoint = async (endpoint: EndpointType) => {
|
||||
const endpointId = endpoint.id
|
||||
|
||||
// Add to loading set
|
||||
setLoadingEndpoints((prev) => new Set(prev).add(endpointId))
|
||||
|
||||
try {
|
||||
let url = ''
|
||||
const method = endpoint.method
|
||||
let data = null
|
||||
|
||||
// For generated endpoints, use the Crud API
|
||||
url = `${import.meta.env.VITE_API_URL}/api/app/crudendpoint/${endpoint.entityName}`
|
||||
|
||||
// Get parameters and modify URL based on operation type
|
||||
const parameters = getEndpointParameters(endpoint)
|
||||
const pathParams = parameters.filter((p) => p.type === 'path')
|
||||
const queryParams = parameters.filter((p) => p.type === 'query')
|
||||
|
||||
// Handle path parameters
|
||||
if (pathParams.length > 0) {
|
||||
const idParam = pathParams.find((p) => p.name === 'id')
|
||||
if (idParam) {
|
||||
url += `/${idParam.value}`
|
||||
}
|
||||
}
|
||||
|
||||
// Handle query parameters
|
||||
if (queryParams.length > 0) {
|
||||
const queryString = queryParams
|
||||
.filter((p) => p.value.trim() !== '')
|
||||
.map((p) => `${p.name}=${encodeURIComponent(p.value)}`)
|
||||
.join('&')
|
||||
if (queryString) {
|
||||
url += `?${queryString}`
|
||||
}
|
||||
}
|
||||
|
||||
// Handle request body
|
||||
if (needsRequestBody(endpoint)) {
|
||||
const requestBodyText = getCurrentRequestBody(endpoint)
|
||||
try {
|
||||
data = requestBodyText ? JSON.parse(requestBodyText) : getRequestExample(endpoint)
|
||||
} catch (e) {
|
||||
throw new Error(`Invalid JSON in request body: ${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
method,
|
||||
url,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: data || undefined,
|
||||
}
|
||||
|
||||
const response = await axios(config)
|
||||
|
||||
setTestResults((prev) => ({
|
||||
...prev,
|
||||
[endpointId]: {
|
||||
success: true,
|
||||
status: response.status,
|
||||
data: response.data,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
}))
|
||||
} catch (error: unknown) {
|
||||
const axiosError = error as {
|
||||
response?: { status?: number; data?: unknown }
|
||||
message?: string
|
||||
}
|
||||
setTestResults((prev) => ({
|
||||
...prev,
|
||||
[endpointId]: {
|
||||
success: false,
|
||||
status: axiosError.response?.status || 0,
|
||||
error: axiosError.response?.data || axiosError.message,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
}))
|
||||
} finally {
|
||||
// Remove from loading set
|
||||
setLoadingEndpoints((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
newSet.delete(endpointId)
|
||||
return newSet
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const stats = {
|
||||
total: allEndpoints.length,
|
||||
custom: 0, // No more custom endpoints
|
||||
generated: generatedEndpoints.filter((e) => e.isActive).length,
|
||||
byMethod: {
|
||||
GET: allEndpoints.filter((e) => e.method === 'GET').length,
|
||||
POST: allEndpoints.filter((e) => e.method === 'POST').length,
|
||||
PUT: allEndpoints.filter((e) => e.method === 'PUT').length,
|
||||
DELETE: allEndpoints.filter((e) => e.method === 'DELETE').length,
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900">
|
||||
{translate('::App.DeveloperKit.CrudEndpoints')}
|
||||
</h1>
|
||||
<p className="text-slate-600">{translate('::App.DeveloperKit.Endpoint.Description')}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 bg-blue-100 text-blue-700 px-3 py-1 rounded-full text-sm font-medium">
|
||||
<FaGlobe className="w-4 h-4" />
|
||||
{translate('::App.DeveloperKit.Endpoint.SwaggerCompatible')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-6 mb-4">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Endpoint.GeneratedCrud')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">{stats.generated}</p>
|
||||
</div>
|
||||
<div className="bg-emerald-100 text-emerald-600 p-3 rounded-lg">
|
||||
<FaDatabase className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Endpoint.GetCount')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">{stats.byMethod.GET}</p>
|
||||
</div>
|
||||
<div className="bg-blue-100 text-blue-600 p-3 rounded-lg">
|
||||
<FaBook className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Endpoint.PostCount')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">{stats.byMethod.POST}</p>
|
||||
</div>
|
||||
<div className="bg-purple-100 text-purple-600 p-3 rounded-lg">
|
||||
<FaPlusCircle className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Endpoint.PutCount')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">{stats.byMethod.PUT}</p>
|
||||
</div>
|
||||
<div className="bg-purple-100 text-purple-600 p-3 rounded-lg">
|
||||
<FaEdit className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Endpoint.DeleteCount')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">{stats.byMethod.DELETE}</p>
|
||||
</div>
|
||||
<div className="bg-purple-100 text-purple-600 p-3 rounded-lg">
|
||||
<FaTrash className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col lg:flex-row gap-4">
|
||||
<div className="flex-1 relative">
|
||||
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-slate-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder={translate('::App.DeveloperKit.Endpoint.SearchPlaceholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<FaFilter className="w-5 h-5 text-slate-500" />
|
||||
<select
|
||||
value={filterMethod}
|
||||
onChange={(e) =>
|
||||
setFilterMethod(e.target.value as 'all' | 'GET' | 'POST' | 'PUT' | 'DELETE')
|
||||
}
|
||||
className="px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
|
||||
>
|
||||
<option value="all">{translate('::App.DeveloperKit.Endpoint.AllMethods')}</option>
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Endpoints List */}
|
||||
{filteredEndpoints.length > 0 ? (
|
||||
<div className="grid grid-cols-1 xl:grid-cols-5 gap-6">
|
||||
{filteredEndpoints.map((endpoint) => (
|
||||
<div
|
||||
key={endpoint.id}
|
||||
className="bg-white rounded-lg border border-slate-200 shadow-sm"
|
||||
>
|
||||
<div
|
||||
className="p-4 cursor-pointer hover:bg-slate-50 transition-colors"
|
||||
onClick={() => {
|
||||
const newSelectedEndpoint = selectedEndpoint === endpoint.id ? null : endpoint.id
|
||||
setSelectedEndpoint(newSelectedEndpoint)
|
||||
if (newSelectedEndpoint === endpoint.id) {
|
||||
initializeEndpointDefaults(endpoint)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Sol taraf */}
|
||||
<div className="flex items-center gap-4">
|
||||
<span
|
||||
className={`px-4 py-1 text-sm font-medium rounded-full border ${getMethodColor(
|
||||
endpoint.method,
|
||||
)}`}
|
||||
>
|
||||
{endpoint.method}
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-slate-900">{endpoint.name}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sağ taraf */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-1">
|
||||
<FaCheckCircle className="w-3 h-3 text-green-500" />
|
||||
<span className="text-sm text-slate-500">
|
||||
{translate('::App.Status.Active')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
<code className="text-sm bg-slate-100 text-slate-700 px-2 py-1 rounded">
|
||||
{endpoint.path}
|
||||
</code>
|
||||
{endpoint.description && (
|
||||
<p className="text-slate-600 text-sm mt-2">{endpoint.description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expanded Details */}
|
||||
{selectedEndpoint === endpoint.id && (
|
||||
<div className="border-t border-slate-200 p-4 bg-slate-50">
|
||||
<div className="grid grid-cols-1 gap-6">
|
||||
{/* Request Details */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-900 mb-3">
|
||||
{translate('::App.DeveloperKit.Endpoint.RequestTitle')}
|
||||
</h4>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
{translate('::App.DeveloperKit.Endpoint.UrlLabel')}
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="flex-1 bg-white border border-slate-300 rounded px-3 py-2 text-sm">
|
||||
{endpoint.method} {endpoint.path}
|
||||
</code>
|
||||
<button
|
||||
onClick={() => copyToClipboard(`${endpoint.method} ${endpoint.path}`)}
|
||||
className="p-2 text-slate-600 hover:text-slate-900 transition-colors"
|
||||
title={translate('::App.DeveloperKit.Endpoint.CopyUrl')}
|
||||
>
|
||||
<FaCopy className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Parameters Section */}
|
||||
{getEndpointParameters(endpoint).length > 0 && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
{translate('::App.DeveloperKit.Endpoint.ParametersLabel')}
|
||||
</label>
|
||||
<div className="space-y-3">
|
||||
{getEndpointParameters(endpoint).map((param) => (
|
||||
<div
|
||||
key={param.name}
|
||||
className="bg-white border border-slate-300 rounded p-3"
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-sm font-medium text-slate-900">
|
||||
{param.name}
|
||||
</span>
|
||||
<span
|
||||
className={`text-xs px-2 py-1 rounded ${
|
||||
param.type === 'path'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'bg-green-100 text-green-700'
|
||||
}`}
|
||||
>
|
||||
{param.type}
|
||||
</span>
|
||||
{param.required && (
|
||||
<span className="text-xs px-2 py-1 rounded bg-red-100 text-red-700">
|
||||
{translate('::App.Required')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{param.description && (
|
||||
<p className="text-xs text-slate-600 mb-2">
|
||||
{param.description}
|
||||
</p>
|
||||
)}
|
||||
<input
|
||||
type="text"
|
||||
value={param.value}
|
||||
onChange={(e) =>
|
||||
updateParameterValue(endpoint.id, param.name, e.target.value)
|
||||
}
|
||||
placeholder={`Enter ${param.name}`}
|
||||
className="w-full px-3 py-2 text-sm border border-slate-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Request Body Section */}
|
||||
{needsRequestBody(endpoint) && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
{translate('::App.DeveloperKit.Endpoint.RequestBodyLabel')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<textarea
|
||||
value={getCurrentRequestBody(endpoint)}
|
||||
onChange={(e) => updateRequestBody(endpoint.id, e.target.value)}
|
||||
placeholder={translate(
|
||||
'::App.DeveloperKit.Endpoint.RequestBodyPlaceholder',
|
||||
)}
|
||||
rows={8}
|
||||
className="w-full px-3 py-2 text-sm border border-slate-300 rounded font-mono focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
<button
|
||||
onClick={() => copyToClipboard(getCurrentRequestBody(endpoint))}
|
||||
className="absolute top-2 right-2 p-1 text-slate-600 hover:text-slate-900 transition-colors"
|
||||
title={translate('::App.DeveloperKit.Endpoint.CopyRequestBody')}
|
||||
>
|
||||
<FaCopy className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Response Details */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-900 mb-3">Response</h4>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
{translate('::App.DeveloperKit.Endpoint.ResponseSuccessLabel')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<pre className="bg-white border border-slate-300 rounded p-3 text-sm overflow-x-auto">
|
||||
{JSON.stringify(getResponseExample(endpoint), null, 2)}
|
||||
</pre>
|
||||
<button
|
||||
onClick={() =>
|
||||
copyToClipboard(
|
||||
JSON.stringify(getResponseExample(endpoint), null, 2),
|
||||
)
|
||||
}
|
||||
className="absolute top-2 right-2 p-1 text-slate-600 hover:text-slate-900 transition-colors"
|
||||
title={translate('::App.DeveloperKit.Endpoint.CopyResponse')}
|
||||
>
|
||||
<FaCopy className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Test Section */}
|
||||
<div className="bg-white border border-slate-300 rounded p-4">
|
||||
<h5 className="font-medium text-slate-900 mb-3">
|
||||
{translate('::App.DeveloperKit.Endpoint.TestSectionTitle')}
|
||||
</h5>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => testEndpoint(endpoint)}
|
||||
disabled={loadingEndpoints.has(endpoint.id)}
|
||||
className="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 disabled:bg-blue-400 disabled:cursor-not-allowed transition-colors text-sm font-medium"
|
||||
>
|
||||
{loadingEndpoints.has(endpoint.id) ? (
|
||||
<FaSyncAlt className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<FaPaperPlane className="w-4 h-4" />
|
||||
)}
|
||||
{loadingEndpoints.has(endpoint.id)
|
||||
? translate('::App.DeveloperKit.Endpoint.SendLoading')
|
||||
: translate('::App.DeveloperKit.Endpoint.SendRequest')}
|
||||
</button>
|
||||
{testResults[endpoint.id] && (
|
||||
<button
|
||||
onClick={() =>
|
||||
setTestResults((prev) => {
|
||||
const newResults = { ...prev }
|
||||
delete newResults[endpoint.id]
|
||||
return newResults
|
||||
})
|
||||
}
|
||||
className="px-3 py-2 text-sm text-slate-600 hover:text-slate-900 transition-colors"
|
||||
>
|
||||
{translate('::App.DeveloperKit.Endpoint.ClearResult')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Test Results */}
|
||||
{testResults[endpoint.id] && (
|
||||
<div className="mt-6 p-4 bg-slate-100 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
{testResults[endpoint.id].success ? (
|
||||
<FaCheckCircle className="w-5 h-5 text-green-500" />
|
||||
) : (
|
||||
<FaExclamationCircle className="w-5 h-5 text-red-500" />
|
||||
)}
|
||||
<h5 className="font-semibold text-slate-900">
|
||||
{translate('::App.DeveloperKit.Endpoint.TestResultLabel')} (
|
||||
{testResults[endpoint.id].status})
|
||||
</h5>
|
||||
<span className="text-xs text-slate-500">
|
||||
{new Date(testResults[endpoint.id].timestamp).toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<pre className="bg-white border border-slate-300 rounded p-3 text-sm overflow-x-auto max-h-96">
|
||||
{JSON.stringify(
|
||||
testResults[endpoint.id].success
|
||||
? testResults[endpoint.id].data
|
||||
: testResults[endpoint.id].error,
|
||||
null,
|
||||
2,
|
||||
)}
|
||||
</pre>
|
||||
<button
|
||||
onClick={() =>
|
||||
copyToClipboard(
|
||||
JSON.stringify(
|
||||
testResults[endpoint.id].success
|
||||
? testResults[endpoint.id].data
|
||||
: testResults[endpoint.id].error,
|
||||
null,
|
||||
2,
|
||||
),
|
||||
)
|
||||
}
|
||||
className="absolute top-2 right-2 p-1 text-slate-600 hover:text-slate-900 transition-colors"
|
||||
title={translate('::App.DeveloperKit.Endpoint.CopyResult')}
|
||||
>
|
||||
<FaCopy className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<div className="max-w-md mx-auto">
|
||||
<div className="bg-slate-100 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
|
||||
<FaBook className="w-8 h-8 text-slate-500" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-slate-900 mb-2">
|
||||
{searchTerm || filterMethod !== 'all'
|
||||
? translate('::App.DeveloperKit.Endpoint.EmptyFilteredTitle')
|
||||
: translate('::App.DeveloperKit.Endpoint.EmptyInitialTitle')}
|
||||
</h3>
|
||||
<p className="text-slate-600">
|
||||
{searchTerm || filterMethod !== 'all'
|
||||
? translate('::App.DeveloperKit.EmptyFilteredDescription')
|
||||
: translate('::App.DeveloperKit.Endpoint.EmptyInitialDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CrudEndpointManager
|
||||
|
|
@ -1,529 +0,0 @@
|
|||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useComponents } from '../../contexts/ComponentContext'
|
||||
import { useEntities } from '../../contexts/EntityContext'
|
||||
import { useSystemHealth } from '../../utils/hooks/useDeveloperKit'
|
||||
import {
|
||||
FaDatabase,
|
||||
FaBolt,
|
||||
FaServer,
|
||||
FaPuzzlePiece,
|
||||
FaCog,
|
||||
FaChartLine,
|
||||
FaCode,
|
||||
FaCheckCircle,
|
||||
FaArrowRight,
|
||||
FaExclamationCircle,
|
||||
FaWifi,
|
||||
FaWindowClose,
|
||||
} from 'react-icons/fa'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
const { components } = useComponents()
|
||||
const { entities, migrations, generatedEndpoints } = useEntities()
|
||||
const { isOnline, lastCheck, recheckHealth } = useSystemHealth()
|
||||
const { translate } = useLocalization()
|
||||
|
||||
const stats = [
|
||||
{
|
||||
name: translate('::App.DeveloperKit.Dashboard.Stats.Entities'),
|
||||
value: entities.filter((e) => e.isActive).length,
|
||||
total: entities.length,
|
||||
icon: FaDatabase,
|
||||
color: 'text-blue-600',
|
||||
bgColor: 'bg-blue-100',
|
||||
href: ROUTES_ENUM.protected.saas.developerKit.entities,
|
||||
},
|
||||
{
|
||||
name: translate('::App.DeveloperKit.Dashboard.Stats.Migrations'),
|
||||
value: migrations.filter((m) => m.status === 'pending').length,
|
||||
total: migrations.length,
|
||||
icon: FaBolt,
|
||||
color: 'text-yellow-600',
|
||||
bgColor: 'bg-yellow-100',
|
||||
href: ROUTES_ENUM.protected.saas.developerKit.migrations,
|
||||
},
|
||||
{
|
||||
name: translate('::App.DeveloperKit.Dashboard.Stats.Apis'),
|
||||
value: generatedEndpoints.filter((e) => e.isActive).length,
|
||||
total: generatedEndpoints.length,
|
||||
icon: FaServer,
|
||||
color: 'text-emerald-600',
|
||||
bgColor: 'bg-emerald-100',
|
||||
href: ROUTES_ENUM.protected.saas.developerKit.endpoints,
|
||||
},
|
||||
{
|
||||
name: translate('::App.DeveloperKit.Components'),
|
||||
value: components?.filter((c) => c.isActive).length,
|
||||
total: components?.length,
|
||||
icon: FaPuzzlePiece,
|
||||
color: 'text-purple-600',
|
||||
bgColor: 'bg-purple-100',
|
||||
href: ROUTES_ENUM.protected.saas.developerKit.components,
|
||||
},
|
||||
]
|
||||
|
||||
const developmentFlow = [
|
||||
{
|
||||
step: 1,
|
||||
title: translate('::App.DeveloperKit.Entity.CreateEntity'),
|
||||
description: translate('::App.DeveloperKit.Dashboard.Flow.CreateEntity.Desc'),
|
||||
icon: FaDatabase,
|
||||
color: 'bg-blue-600',
|
||||
href: ROUTES_ENUM.protected.saas.developerKit.entitiesNew,
|
||||
status: 'ready',
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: translate('::App.DeveloperKit.Dashboard.Flow.GenerateMigration'),
|
||||
description: translate('::App.DeveloperKit.Dashboard.Flow.GenerateMigration.Desc'),
|
||||
icon: FaBolt,
|
||||
color: 'bg-yellow-600',
|
||||
href: ROUTES_ENUM.protected.saas.developerKit.migrations,
|
||||
status: entities.some((e) => e.migrationStatus === 'pending') ? 'action-needed' : 'ready',
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: translate('::App.DeveloperKit.Dashboard.Flow.ApplyMigration'),
|
||||
description: translate('::App.DeveloperKit.Dashboard.Flow.ApplyMigration.Desc'),
|
||||
icon: FaCheckCircle,
|
||||
color: 'bg-green-600',
|
||||
href: ROUTES_ENUM.protected.saas.developerKit.migrations,
|
||||
status: migrations.some((m) => m.status === 'pending') ? 'action-needed' : 'ready',
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
title: translate('::App.DeveloperKit.Dashboard.Flow.GenerateApi'),
|
||||
description: translate('::App.DeveloperKit.Dashboard.Flow.GenerateApi.Desc'),
|
||||
icon: FaServer,
|
||||
color: 'bg-emerald-600',
|
||||
href: ROUTES_ENUM.protected.saas.developerKit.endpoints,
|
||||
status: 'ready',
|
||||
},
|
||||
{
|
||||
step: 5,
|
||||
title: translate('::App.DeveloperKit.Dashboard.Flow.BuildComponent'),
|
||||
description: translate('::App.DeveloperKit.Dashboard.Flow.BuildComponent.Desc'),
|
||||
icon: FaPuzzlePiece,
|
||||
color: 'bg-purple-600',
|
||||
href: ROUTES_ENUM.protected.saas.developerKit.componentsNew,
|
||||
status: 'ready',
|
||||
},
|
||||
]
|
||||
|
||||
const recentEntities = entities.slice(0, 3)
|
||||
const recentMigrations = migrations.slice(0, 3)
|
||||
const recentEndpoints = [
|
||||
...generatedEndpoints.map((e) => ({
|
||||
id: e.id,
|
||||
name: `${e.entityName} ${e.operationType}`,
|
||||
method: e.method,
|
||||
path: e.path,
|
||||
description: `Generated ${e.operationType} for ${e.entityName}`,
|
||||
isActive: e.isActive,
|
||||
lastModificationTime: e.lastModificationTime,
|
||||
creationTime: e.creationTime,
|
||||
})),
|
||||
].slice(0, 3)
|
||||
|
||||
const systemHealth = [
|
||||
{
|
||||
name: translate('::App.DeveloperKit.Dashboard.SystemHealth.Frontend'),
|
||||
status: translate('::App.DeveloperKit.Dashboard.SystemHealth.Healthy'),
|
||||
icon: FaCode,
|
||||
},
|
||||
{
|
||||
name: translate('::App.DeveloperKit.Dashboard.SystemHealth.Backend'),
|
||||
status: isOnline
|
||||
? translate('::App.DeveloperKit.Dashboard.SystemHealth.Healthy')
|
||||
: translate('::App.DeveloperKit.Dashboard.SystemHealth.Offline'),
|
||||
icon: FaServer,
|
||||
},
|
||||
{
|
||||
name: translate('::ListForms.ListFormEdit.TabDatabase'),
|
||||
status: isOnline
|
||||
? translate('::App.DeveloperKit.Dashboard.SystemHealth.Healthy')
|
||||
: translate('::App.DeveloperKit.Dashboard.SystemHealth.Unknown'),
|
||||
icon: FaDatabase,
|
||||
},
|
||||
{
|
||||
name: translate('::App.DeveloperKit.Dashboard.SystemHealth.Migrations'),
|
||||
status: migrations.some((m) => m.status === 'failed')
|
||||
? translate('::App.DeveloperKit.Dashboard.SystemHealth.Warning')
|
||||
: translate('::App.DeveloperKit.Dashboard.SystemHealth.Healthy'),
|
||||
icon: FaBolt,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900">
|
||||
{translate('::App.Coordinator.Classroom.Dashboard')}
|
||||
</h1>
|
||||
<p className="text-slate-600">{translate('::App.DeveloperKit.Dashboard.Description')}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={recheckHealth}
|
||||
className={`flex items-center gap-2 px-3 py-1 rounded-full text-sm font-medium transition-colors duration-200 ${
|
||||
isOnline
|
||||
? 'bg-green-100 text-green-700 hover:bg-green-200'
|
||||
: 'bg-red-100 text-red-700 hover:bg-red-200'
|
||||
}`}
|
||||
title={`Son kontrol: ${lastCheck.toLocaleTimeString()}`}
|
||||
>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
isOnline ? 'bg-green-500 animate-pulse' : 'bg-red-500 animate-pulse'
|
||||
}`}
|
||||
/>
|
||||
{isOnline ? (
|
||||
<>
|
||||
<FaWifi className="w-4 h-4" />
|
||||
{translate('::App.DeveloperKit.Dashboard.SystemHealth.Online')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaWindowClose className="w-4 h-4" />
|
||||
{translate('::App.DeveloperKit.Dashboard.SystemHealth.OfflineStatus')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{stats.map((stat) => {
|
||||
const Icon = stat.icon
|
||||
return (
|
||||
<Link
|
||||
key={stat.name}
|
||||
to={stat.href}
|
||||
className="bg-white rounded-xl shadow-sm border border-slate-200 p-4 hover:shadow-md transition-all duration-200 group"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div
|
||||
className={`${stat.bgColor} ${stat.color} p-3 rounded-lg group-hover:scale-110 transition-transform`}
|
||||
>
|
||||
<Icon className="w-6 h-6" />
|
||||
</div>
|
||||
<FaArrowRight className="w-4 h-4 text-slate-400 group-hover:text-slate-600 transition-colors" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">{stat.name}</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">
|
||||
{stat.value}
|
||||
<span className="text-sm font-normal text-slate-500 ml-1">/ {stat.total}</span>
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Development Flow */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-4">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<FaCog className="w-6 h-6 text-blue-600" />
|
||||
<h2 className="text-xl font-semibold text-slate-900">
|
||||
{translate('::App.DeveloperKit.Dashboard.Flow.Title')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-5 gap-8">
|
||||
{developmentFlow.map((flow, index) => {
|
||||
const Icon = flow.icon
|
||||
return (
|
||||
<Link key={flow.step} to={flow.href} className="group relative">
|
||||
<div
|
||||
className={`${flow.color} text-white p-4 rounded-lg transition-all duration-200 transform group-hover:scale-105`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="bg-white bg-opacity-20 rounded-lg p-2">
|
||||
<Icon className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="bg-white bg-opacity-20 rounded-full w-8 h-8 flex items-center justify-center">
|
||||
<span className="text-sm font-bold">{flow.step}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="font-semibold text-lg mb-2 text-white">{flow.title}</h3>
|
||||
<p className="text-xs opacity-90">{flow.description}</p>
|
||||
|
||||
{flow.status === 'action-needed' && (
|
||||
<div className="absolute -top-2 -right-2">
|
||||
<div className="bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center">
|
||||
<FaExclamationCircle className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{index < developmentFlow.length - 1 && (
|
||||
<div className="hidden lg:block absolute top-1/2 -right-3 transform -translate-y-1/2">
|
||||
<FaArrowRight className="w-6 h-6 text-slate-300" />
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
|
||||
{/* System Health */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-4">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<FaCog className="w-5 h-5 text-green-500" />
|
||||
<h3 className="text-lg font-semibold text-slate-900">
|
||||
{translate('::App.DeveloperKit.Dashboard.SystemHealth.Title')}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{systemHealth.map((system) => {
|
||||
const Icon = system.icon
|
||||
return (
|
||||
<div
|
||||
key={system.name}
|
||||
className="flex items-center justify-between p-3 bg-slate-50 rounded-lg"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Icon className="w-4 h-4 text-slate-600" />
|
||||
<span className="font-medium text-slate-900">{system.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
system.status === 'healthy'
|
||||
? 'bg-green-500'
|
||||
: system.status === 'warning'
|
||||
? 'bg-yellow-500'
|
||||
: system.status === 'offline'
|
||||
? 'bg-red-500'
|
||||
: 'bg-gray-500'
|
||||
}`}
|
||||
/>
|
||||
<span
|
||||
className={`text-sm capitalize ${
|
||||
system.status === 'healthy'
|
||||
? 'text-green-600'
|
||||
: system.status === 'warning'
|
||||
? 'text-yellow-600'
|
||||
: system.status === 'offline'
|
||||
? 'text-red-600'
|
||||
: 'text-gray-600'
|
||||
}`}
|
||||
>
|
||||
{system.status === 'offline' ? 'Offline' : system.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Entities */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-slate-900">
|
||||
{translate('::App.DeveloperKit.Dashboard.RecentEntities.Title')}
|
||||
</h3>
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.entities}
|
||||
className="text-blue-600 hover:text-blue-700 text-sm font-medium"
|
||||
>
|
||||
{translate('::App.DeveloperKit.Dashboard.ViewAll')}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{recentEntities.length > 0 ? (
|
||||
recentEntities.map((entity) => (
|
||||
<div
|
||||
key={entity.id}
|
||||
className="flex items-center justify-between p-3 bg-slate-50 rounded-lg hover:bg-slate-100 transition-colors"
|
||||
>
|
||||
<div>
|
||||
<p className="font-medium text-slate-900">{entity.displayName}</p>
|
||||
<p className="text-sm text-slate-500">
|
||||
{entity.fields.length} fields • {entity.migrationStatus}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
entity.migrationStatus === 'applied'
|
||||
? 'bg-green-500'
|
||||
: entity.migrationStatus === 'pending'
|
||||
? 'bg-yellow-500'
|
||||
: 'bg-red-500'
|
||||
}`}
|
||||
/>
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.entitiesEdit.replace(
|
||||
':id',
|
||||
entity.id,
|
||||
)}
|
||||
className="text-blue-600 hover:text-blue-700"
|
||||
>
|
||||
<FaDatabase className="w-4 h-4" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<FaDatabase className="w-12 h-12 text-slate-300 mx-auto mb-2" />
|
||||
<p className="text-slate-500 mb-2">
|
||||
{translate('::App.DeveloperKit.Dashboard.Empty.Entity')}
|
||||
</p>
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.entitiesNew}
|
||||
className="text-blue-600 hover:text-blue-700 text-sm font-medium"
|
||||
>
|
||||
Create your first entity
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Migrations */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-slate-900">
|
||||
{translate('::App.DeveloperKit.Dashboard.RecentMigrations.Title')}
|
||||
</h3>
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.migrations}
|
||||
className="text-yellow-600 hover:text-yellow-700 text-sm font-medium"
|
||||
>
|
||||
{translate('::App.DeveloperKit.Dashboard.ViewAll')}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{recentMigrations.length > 0 ? (
|
||||
recentMigrations.map((migration) => (
|
||||
<div
|
||||
key={migration.id}
|
||||
className="flex items-center justify-between p-3 bg-slate-50 rounded-lg hover:bg-slate-100 transition-colors"
|
||||
>
|
||||
<div>
|
||||
<p className="font-medium text-slate-900">{migration.entityName}</p>
|
||||
<p className="text-sm text-slate-500">
|
||||
{migration.status} • {new Date(migration.creationTime).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
migration.status === 'applied'
|
||||
? 'bg-green-500'
|
||||
: migration.status === 'pending'
|
||||
? 'bg-yellow-500'
|
||||
: 'bg-red-500'
|
||||
}`}
|
||||
/>
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.migrations}
|
||||
className="text-yellow-600 hover:text-yellow-700"
|
||||
>
|
||||
<FaBolt className="w-4 h-4" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<FaBolt className="w-12 h-12 text-slate-300 mx-auto mb-2" />
|
||||
<p className="text-slate-500 mb-2">
|
||||
{translate('::App.DeveloperKit.Dashboard.Empty.Migration')}
|
||||
</p>
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.entitiesNew}
|
||||
className="text-yellow-600 hover:text-yellow-700 text-sm font-medium"
|
||||
>
|
||||
{translate('::App.DeveloperKit.Dashboard.Action.GenerateMigrations')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent API Endpoints */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-slate-900">
|
||||
{translate('::App.DeveloperKit.Dashboard.RecentEndpoints.Title')}
|
||||
</h3>
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.endpoints}
|
||||
className="text-emerald-600 hover:text-emerald-700 text-sm font-medium"
|
||||
>
|
||||
{translate('::App.DeveloperKit.Dashboard.ViewAll')}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{recentEndpoints.length > 0 ? (
|
||||
recentEndpoints.map((endpoint) => (
|
||||
<div
|
||||
key={endpoint.id}
|
||||
className="flex items-center justify-between p-3 bg-slate-50 rounded-lg hover:bg-slate-100 transition-colors"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded ${
|
||||
endpoint.method === 'GET'
|
||||
? 'bg-blue-100 text-blue-800'
|
||||
: endpoint.method === 'POST'
|
||||
? 'bg-green-100 text-green-800'
|
||||
: endpoint.method === 'PUT'
|
||||
? 'bg-yellow-100 text-yellow-800'
|
||||
: 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{endpoint.method}
|
||||
</span>
|
||||
<p className="font-medium text-slate-900 text-sm">{endpoint.name}</p>
|
||||
</div>
|
||||
<p className="text-xs text-slate-500">{endpoint.path}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
endpoint.isActive ? 'bg-green-500' : 'bg-slate-300'
|
||||
}`}
|
||||
/>
|
||||
<Link to="/docs" className="text-emerald-600 hover:text-emerald-700">
|
||||
<FaServer className="w-4 h-4" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<FaServer className="w-12 h-12 text-slate-300 mx-auto mb-2" />
|
||||
<p className="text-slate-500 mb-2">
|
||||
{translate('::App.DeveloperKit.Dashboard.Empty.Endpoint')}
|
||||
</p>
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.endpointsNew}
|
||||
className="text-emerald-600 hover:text-emerald-700 text-sm font-medium"
|
||||
>
|
||||
{translate('::App.DeveloperKit.Dashboard.Action.CreateEntity')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Dashboard
|
||||
|
|
@ -1,678 +0,0 @@
|
|||
import React, { useState, useEffect, useMemo } from 'react'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { useEntities, EntityFieldType } from '../../contexts/EntityContext'
|
||||
import {
|
||||
FaSave,
|
||||
FaArrowLeft,
|
||||
FaPlus,
|
||||
FaTrashAlt,
|
||||
FaDatabase,
|
||||
FaCog,
|
||||
FaTable,
|
||||
FaColumns,
|
||||
} from 'react-icons/fa'
|
||||
import { CustomEntityField } from '@/proxy/developerKit/models'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { Formik, Form, Field, FieldProps, FieldArray } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { FormItem, Input, Select, Checkbox, FormContainer, Button } from '@/components/ui'
|
||||
import { SelectBoxOption } from '@/types/shared'
|
||||
import { MenuService } from '@/services/menu.service'
|
||||
import { MenuDto } from '@/proxy/menus/models'
|
||||
|
||||
// Validation schema
|
||||
const validationSchema = Yup.object({
|
||||
menu: Yup.string().required('Menu is required'),
|
||||
name: Yup.string().required('Entity name is required'),
|
||||
displayName: Yup.string().required('Display name is required'),
|
||||
tableName: Yup.string().required('Table name is required'),
|
||||
description: Yup.string(),
|
||||
fields: Yup.array()
|
||||
.of(
|
||||
Yup.object({
|
||||
name: Yup.string().required('Field name is required'),
|
||||
type: Yup.string().required('Field type is required'),
|
||||
isRequired: Yup.boolean(),
|
||||
maxLength: Yup.number().nullable(),
|
||||
isUnique: Yup.boolean(),
|
||||
defaultValue: Yup.string().notRequired(),
|
||||
description: Yup.string().notRequired(),
|
||||
displayOrder: Yup.number().required(),
|
||||
}),
|
||||
)
|
||||
.min(1, 'At least one field is required'),
|
||||
isActive: Yup.boolean(),
|
||||
isFullAuditedEntity: Yup.boolean(),
|
||||
isMultiTenant: Yup.boolean(),
|
||||
})
|
||||
|
||||
const EntityEditor: React.FC = () => {
|
||||
const { id } = useParams()
|
||||
const navigate = useNavigate()
|
||||
const { translate } = useLocalization()
|
||||
const { getEntity, addEntity, updateEntity } = useEntities()
|
||||
|
||||
const isEditing = !!id
|
||||
|
||||
// Menu options state
|
||||
const [menuOptions, setMenuOptions] = useState<SelectBoxOption[]>([])
|
||||
|
||||
// Fetch menu options on mount
|
||||
useEffect(() => {
|
||||
const fetchMenuOptions = async () => {
|
||||
try {
|
||||
const menuService = new MenuService()
|
||||
const response = await menuService.getListMainMenu()
|
||||
|
||||
if (response.data && Array.isArray(response.data)) {
|
||||
const options = response.data.map((menuItem: any) => ({
|
||||
value: menuItem.shortName,
|
||||
label: translate('::' + menuItem.displayName),
|
||||
}))
|
||||
setMenuOptions(options)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching menu options:', error)
|
||||
}
|
||||
}
|
||||
|
||||
fetchMenuOptions()
|
||||
}, [])
|
||||
|
||||
// Initial values for Formik
|
||||
const [initialValues, setInitialValues] = useState({
|
||||
menu: '',
|
||||
name: '',
|
||||
displayName: '',
|
||||
tableName: '',
|
||||
description: '',
|
||||
fields: [
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
entityId: id || '',
|
||||
name: 'Name',
|
||||
type: 'string' as EntityFieldType,
|
||||
isRequired: true,
|
||||
maxLength: 100,
|
||||
description: 'Entity name',
|
||||
displayOrder: 1,
|
||||
},
|
||||
] as CustomEntityField[],
|
||||
isActive: true,
|
||||
isFullAuditedEntity: true,
|
||||
isMultiTenant: true,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing && id) {
|
||||
const entity = getEntity(id)
|
||||
if (entity) {
|
||||
// Ensure fields are sorted by displayOrder and normalized to sequential values
|
||||
const sortedFields = (entity.fields || [])
|
||||
.slice()
|
||||
.sort((a, b) => (a.displayOrder ?? 0) - (b.displayOrder ?? 0))
|
||||
.map((f, idx) => ({ ...f, displayOrder: f.displayOrder ?? idx + 1 }))
|
||||
|
||||
setInitialValues({
|
||||
menu: entity.menu,
|
||||
name: entity.name,
|
||||
displayName: entity.displayName,
|
||||
tableName: entity.tableName,
|
||||
description: entity.description || '',
|
||||
fields: sortedFields,
|
||||
isActive: entity.isActive,
|
||||
isFullAuditedEntity: entity.isFullAuditedEntity,
|
||||
isMultiTenant: entity.isMultiTenant,
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [id, isEditing, getEntity])
|
||||
|
||||
const handleSubmit = async (values: typeof initialValues, { setSubmitting }: any) => {
|
||||
try {
|
||||
const sanitizedFields = values.fields.map((f) => {
|
||||
// send both `displayOrder` (frontend proxy) and `order` (backend DTO) to be safe
|
||||
const sanitized: any = {
|
||||
...(f.id && isEditing ? { id: f.id } : {}),
|
||||
name: f.name.trim(),
|
||||
type: f.type,
|
||||
isRequired: f.isRequired,
|
||||
maxLength: f.maxLength,
|
||||
isUnique: f.isUnique || false,
|
||||
defaultValue: f.defaultValue,
|
||||
description: f.description,
|
||||
displayOrder: f.displayOrder,
|
||||
order: f.displayOrder,
|
||||
}
|
||||
|
||||
return sanitized
|
||||
})
|
||||
|
||||
const entityData = {
|
||||
menu: values.menu.trim(),
|
||||
name: values.name.trim(),
|
||||
displayName: values.displayName.trim(),
|
||||
tableName: values.tableName.trim(),
|
||||
description: values.description.trim(),
|
||||
fields: sanitizedFields,
|
||||
isActive: values.isActive,
|
||||
isFullAuditedEntity: values.isFullAuditedEntity,
|
||||
isMultiTenant: values.isMultiTenant,
|
||||
}
|
||||
|
||||
if (isEditing && id) {
|
||||
await updateEntity(id, entityData)
|
||||
} else {
|
||||
await addEntity(entityData)
|
||||
}
|
||||
|
||||
navigate(ROUTES_ENUM.protected.saas.developerKit.entities, { replace: true })
|
||||
} catch (error) {
|
||||
console.error('Error saving entity:', error)
|
||||
alert('Failed to save entity. Please try again.')
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to generate table name
|
||||
const generateTableName = (menuValue: string, entityName: string): string => {
|
||||
if (!menuValue || !entityName) return ''
|
||||
const selectedMenu = menuOptions.find((opt) => opt.value === menuValue)
|
||||
if (!selectedMenu) return ''
|
||||
return `${selectedMenu.value}_D_${entityName}`
|
||||
}
|
||||
|
||||
const fieldTypes = [
|
||||
{ value: 'string', label: 'String' },
|
||||
{ value: 'number', label: 'Number' },
|
||||
{ value: 'decimal', label: 'Decimal' },
|
||||
{ value: 'boolean', label: 'Boolean' },
|
||||
{ value: 'date', label: 'Date' },
|
||||
{ value: 'guid', label: 'Guid' },
|
||||
]
|
||||
|
||||
return (
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ values, touched, errors, isSubmitting, setFieldValue, submitForm, isValid }) => (
|
||||
<>
|
||||
{/* Enhanced Header */}
|
||||
<div className="bg-white shadow border-b border-slate-200 sticky top-0 z-10">
|
||||
<div className="px-4 py-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate(ROUTES_ENUM.protected.saas.developerKit.entities)}
|
||||
className="flex items-center gap-2 text-slate-600 hover:text-blue-600 hover:bg-blue-50 px-3 py-2 rounded-lg transition-all duration-200"
|
||||
>
|
||||
<FaArrowLeft className="w-4 h-4" />
|
||||
<span className="text-sm">
|
||||
{translate('::App.DeveloperKit.EntityEditor.Back')}
|
||||
</span>
|
||||
</button>
|
||||
<div className="h-6 w-px bg-slate-300"></div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="bg-gradient-to-r from-green-500 to-blue-600 p-1 rounded">
|
||||
<FaDatabase className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-slate-900">
|
||||
{isEditing
|
||||
? `${translate('::App.DeveloperKit.EntityEditor.Title.Edit')} - ${values.name || initialValues.name || 'Entity'}`
|
||||
: translate('::App.DeveloperKit.Entity.CreateEntity')}
|
||||
</h1>
|
||||
<p className="text-sm text-slate-600">
|
||||
{isEditing ? 'Modify your entity' : 'Create a new entity'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Save Button in Header */}
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={submitForm}
|
||||
disabled={isSubmitting || !isValid}
|
||||
className={`
|
||||
flex items-center gap-1
|
||||
px-2 py-1.5 rounded text-sm transition-all duration-200
|
||||
text-white
|
||||
bg-gradient-to-r from-green-600 to-green-700
|
||||
hover:from-green-700 hover:to-green-800
|
||||
disabled:from-gray-400 disabled:to-gray-500
|
||||
disabled:text-gray-200
|
||||
disabled:cursor-not-allowed
|
||||
disabled:hover:from-gray-400 disabled:hover:to-gray-500
|
||||
`}
|
||||
>
|
||||
<FaSave className="w-3 h-3" />
|
||||
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form className="grid grid-cols-1 lg:grid-cols-4 gap-4 pt-2">
|
||||
{/* Migration Status Info Banner */}
|
||||
{isEditing && id && getEntity(id)?.migrationStatus === 'applied' && (
|
||||
<div className="col-span-4 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="bg-yellow-100 p-2 rounded-lg">
|
||||
<FaDatabase className="w-5 h-5 text-yellow-600" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-sm font-semibold text-yellow-900 mb-1">
|
||||
Migration Applied - Changes Will Require New Migration
|
||||
</h3>
|
||||
<p className="text-xs text-yellow-700">
|
||||
This entity has been migrated to the database. Any structural changes you make
|
||||
will reset the migration status to "pending", and you'll need to generate and
|
||||
apply a new migration to update the database schema.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Basic Entity Information */}
|
||||
<div className="space-y-4 col-span-1">
|
||||
<div className="bg-white rounded-lg shadow-sm border border-slate-200 p-3">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<div className="bg-blue-100 p-1.5 rounded-lg">
|
||||
<FaCog className="w-4 h-4 text-blue-600" />
|
||||
</div>
|
||||
<h2 className="text-sm font-semibold text-slate-900">Entity Settings</h2>
|
||||
</div>
|
||||
|
||||
<FormContainer size="sm">
|
||||
<FormItem
|
||||
label={translate('::App.DeveloperKit.EntityEditor.MenuName')}
|
||||
invalid={!!(errors.menu && touched.menu)}
|
||||
errorMessage={errors.menu as string}
|
||||
>
|
||||
<Field name="menu">
|
||||
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
||||
<Select
|
||||
field={field}
|
||||
form={form}
|
||||
options={menuOptions}
|
||||
isClearable={true}
|
||||
value={menuOptions.filter((option) => option.value === values.menu)}
|
||||
onChange={(option) => {
|
||||
const newMenuValue = option?.value || ''
|
||||
form.setFieldValue(field.name, newMenuValue)
|
||||
// Update table name when menu changes
|
||||
if (values.name && newMenuValue) {
|
||||
const newTableName = generateTableName(newMenuValue, values.name)
|
||||
form.setFieldValue('tableName', newTableName)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
label={translate('::App.DeveloperKit.EntityEditor.EntityName')}
|
||||
invalid={!!(errors.name && touched.name)}
|
||||
errorMessage={errors.name as string}
|
||||
>
|
||||
<Field name="name">
|
||||
{({ field }: FieldProps) => (
|
||||
<Input
|
||||
{...field}
|
||||
onBlur={(e) => {
|
||||
field.onBlur(e)
|
||||
const entityName = e.target.value.trim()
|
||||
// Update table name based on menu prefix and entity name
|
||||
if (entityName && values.menu) {
|
||||
const newTableName = generateTableName(values.menu, entityName)
|
||||
setFieldValue('tableName', newTableName)
|
||||
}
|
||||
// Update display name if empty
|
||||
if (entityName && !values.displayName) {
|
||||
setFieldValue('displayName', entityName)
|
||||
}
|
||||
}}
|
||||
placeholder="e.g., Product, User, Order"
|
||||
className="px-2 py-1.5 bg-slate-50 focus:bg-white transition-all duration-200 text-sm h-7"
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceTableName')}
|
||||
invalid={!!(errors.tableName && touched.tableName)}
|
||||
errorMessage={errors.tableName as string}
|
||||
>
|
||||
<Field
|
||||
name="tableName"
|
||||
component={Input}
|
||||
placeholder="e.g., Adm_D_Product, Net_D_User"
|
||||
disabled={true}
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-1">
|
||||
Format:{' '}
|
||||
{values.menu
|
||||
? `${menuOptions.find((opt) => opt.value === values.menu)?.value || '[Prefix]'}_D_`
|
||||
: '[Prefix]_D_'}
|
||||
EntityName
|
||||
</p>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
label={translate('::Abp.Identity.OrganizationUnit.DisplayName')}
|
||||
invalid={!!(errors.displayName && touched.displayName)}
|
||||
errorMessage={errors.displayName as string}
|
||||
>
|
||||
<Field
|
||||
name="displayName"
|
||||
component={Input}
|
||||
placeholder="Display name for UI (e.g., Product, User)"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-1">
|
||||
User-friendly name shown in the interface
|
||||
</p>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
label={translate('::ListForms.ListFormEdit.DetailsDescription')}
|
||||
invalid={!!(errors.description && touched.description)}
|
||||
errorMessage={errors.description as string}
|
||||
>
|
||||
<Field
|
||||
name="description"
|
||||
component={Input}
|
||||
placeholder="Brief description of this entity"
|
||||
textArea={true}
|
||||
rows={2}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label={translate('::App.Status.Active')}>
|
||||
<Field name="isActive" component={Checkbox} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="Full Audited Entity">
|
||||
<Field name="isFullAuditedEntity" component={Checkbox} />
|
||||
<p className="text-xs text-slate-500 mt-1">
|
||||
Includes CreationTime, CreatorId, LastModificationTime, LastModifierId,
|
||||
IsDeleted, DeleterId, DeletionTime
|
||||
</p>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="Multi-Tenant">
|
||||
<Field name="isMultiTenant" component={Checkbox} />
|
||||
<p className="text-xs text-slate-500 mt-1">
|
||||
Adds TenantId column for multi-tenancy support
|
||||
</p>
|
||||
</FormItem>
|
||||
</FormContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Fields Section */}
|
||||
<div className="space-y-4 col-span-3">
|
||||
<div className="bg-white rounded-lg shadow-sm border border-slate-200 p-3">
|
||||
<FormContainer size="sm">
|
||||
<FieldArray name="fields">
|
||||
{({ push, remove }) => (
|
||||
<>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="bg-green-100 p-1.5 rounded">
|
||||
<FaColumns className="w-4 h-4 text-green-600" />
|
||||
</div>
|
||||
<h2 className="text-sm font-semibold text-slate-900">
|
||||
{translate('::App.DeveloperKit.EntityEditor.Fields')}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
push({
|
||||
id: crypto.randomUUID(),
|
||||
entityId: id || '',
|
||||
name: '',
|
||||
type: 'string',
|
||||
isRequired: false,
|
||||
description: '',
|
||||
displayOrder: (values.fields?.length ?? 0) + 1,
|
||||
})
|
||||
}
|
||||
className={`
|
||||
flex items-center gap-1
|
||||
px-2 py-1.5 rounded text-sm transition-all duration-200
|
||||
text-white
|
||||
bg-gradient-to-r from-blue-600 to-blue-700
|
||||
hover:from-blue-700 hover:to-blue-800
|
||||
disabled:from-gray-400 disabled:to-gray-500
|
||||
disabled:text-gray-200
|
||||
disabled:cursor-not-allowed
|
||||
disabled:hover:from-gray-400 disabled:hover:to-gray-500
|
||||
`}
|
||||
>
|
||||
<FaPlus className="w-2.5 h-2.5" />
|
||||
{translate('::App.DeveloperKit.EntityEditor.AddField')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-12 gap-2 mb-2">
|
||||
<div className="col-span-1 font-bold">Order *</div>
|
||||
|
||||
<div className="col-span-2 font-bold">
|
||||
{translate('::ListForms.ListFormFieldEdit.FieldName')} *
|
||||
</div>
|
||||
|
||||
<div className="col-span-1 font-bold">
|
||||
{translate('::ListForms.ListFormEdit.Type')} *
|
||||
</div>
|
||||
|
||||
<div className="col-span-2 font-bold">
|
||||
{translate('::ListForms.ListFormEdit.ExtraDefaultValue')}
|
||||
</div>
|
||||
|
||||
<div className="font-bold">
|
||||
{translate('::App.DeveloperKit.EntityEditor.MaxLength')}
|
||||
</div>
|
||||
|
||||
<div className="col-span-2 font-bold">
|
||||
{translate('::ListForms.ListFormEdit.DetailsDescription')}
|
||||
</div>
|
||||
|
||||
<div className="items-center font-bold">
|
||||
{translate('::App.Required')}
|
||||
</div>
|
||||
|
||||
<div className="items-center font-bold">
|
||||
{translate('::App.DeveloperKit.EntityEditor.Unique')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{values.fields.map((field, index) => (
|
||||
<div key={field.id || `new-${index}`}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-12 gap-2">
|
||||
<FormItem className="col-span-1">
|
||||
<Field
|
||||
type="number"
|
||||
name={`fields.${index}.displayOrder`}
|
||||
component={Input}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
invalid={
|
||||
!!(
|
||||
errors.fields &&
|
||||
(errors.fields as any)[index]?.name &&
|
||||
touched.fields &&
|
||||
(touched.fields as any)[index]?.name
|
||||
)
|
||||
}
|
||||
errorMessage={(errors.fields as any)?.[index]?.name as string}
|
||||
className="col-span-2"
|
||||
>
|
||||
<Field
|
||||
name={`fields.${index}.name`}
|
||||
component={Input}
|
||||
placeholder="e.g., Name, Email, Age"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
invalid={
|
||||
!!(
|
||||
errors.fields &&
|
||||
(errors.fields as any)[index]?.type &&
|
||||
touched.fields &&
|
||||
(touched.fields as any)[index]?.type
|
||||
)
|
||||
}
|
||||
errorMessage={(errors.fields as any)?.[index]?.type as string}
|
||||
className="col-span-1"
|
||||
>
|
||||
<Field name={`fields.${index}.type`}>
|
||||
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
||||
<Select
|
||||
field={field}
|
||||
form={form}
|
||||
options={fieldTypes.map((type) => ({
|
||||
value: type.value,
|
||||
label: type.label,
|
||||
}))}
|
||||
value={fieldTypes
|
||||
.map((type) => ({ value: type.value, label: type.label }))
|
||||
.filter((option) => option.value === field.value)}
|
||||
onChange={(option) =>
|
||||
form.setFieldValue(field.name, option?.value)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
invalid={
|
||||
!!(
|
||||
errors.fields &&
|
||||
(errors.fields as any)[index]?.defaultValue &&
|
||||
touched.fields &&
|
||||
(touched.fields as any)[index]?.defaultValue
|
||||
)
|
||||
}
|
||||
errorMessage={
|
||||
(errors.fields as any)?.[index]?.defaultValue as string
|
||||
}
|
||||
className="col-span-2"
|
||||
>
|
||||
<Field
|
||||
name={`fields.${index}.defaultValue`}
|
||||
component={Input}
|
||||
placeholder="Optional default value"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
invalid={
|
||||
!!(
|
||||
errors.fields &&
|
||||
(errors.fields as any)[index]?.maxLength &&
|
||||
touched.fields &&
|
||||
(touched.fields as any)[index]?.maxLength
|
||||
)
|
||||
}
|
||||
errorMessage={(errors.fields as any)?.[index]?.maxLength as string}
|
||||
>
|
||||
<Field
|
||||
name={`fields.${index}.maxLength`}
|
||||
component={Input}
|
||||
type="number"
|
||||
placeholder="e.g., 100"
|
||||
disabled={field.type !== 'string'}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
invalid={
|
||||
!!(
|
||||
errors.fields &&
|
||||
(errors.fields as any)[index]?.description &&
|
||||
touched.fields &&
|
||||
(touched.fields as any)[index]?.description
|
||||
)
|
||||
}
|
||||
errorMessage={
|
||||
(errors.fields as any)?.[index]?.description as string
|
||||
}
|
||||
className="col-span-2"
|
||||
>
|
||||
<Field
|
||||
name={`fields.${index}.description`}
|
||||
component={Input}
|
||||
placeholder="Field description"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem className="items-center">
|
||||
<Field name={`fields.${index}.isRequired`} component={Checkbox} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem className="items-center">
|
||||
<Field name={`fields.${index}.isUnique`} component={Checkbox} />
|
||||
</FormItem>
|
||||
|
||||
<Button
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
remove(index)
|
||||
const newFields = values.fields ? [...values.fields] : []
|
||||
newFields.splice(index, 1)
|
||||
newFields.forEach((f, i) => {
|
||||
f.displayOrder = i + 1
|
||||
})
|
||||
setFieldValue('fields', newFields)
|
||||
}}
|
||||
className="!px-0 !py-0 !border-0 text-red-600 hover:text-red-800 rounded transition-all duration-200"
|
||||
title="Remove field"
|
||||
>
|
||||
<FaTrashAlt className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{values.fields.length === 0 && (
|
||||
<div className="text-center py-4 bg-slate-50 rounded border-2 border-dashed border-slate-300">
|
||||
<FaTable className="w-8 h-8 mx-auto mb-2 text-slate-400" />
|
||||
<h3 className="text-sm font-medium text-slate-600 mb-1">
|
||||
{translate('::App.DeveloperKit.EntityEditor.NoFields')}
|
||||
</h3>
|
||||
<p className="text-sm text-slate-500">
|
||||
{translate('::App.DeveloperKit.EntityEditor.NoFieldsDescription')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</FieldArray>
|
||||
</FormContainer>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
|
||||
export default EntityEditor
|
||||
|
|
@ -1,438 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useEntities } from '../../contexts/EntityContext'
|
||||
import {
|
||||
FaPlus,
|
||||
FaSearch,
|
||||
FaEdit,
|
||||
FaTrashAlt,
|
||||
FaEye,
|
||||
FaEyeSlash,
|
||||
FaFilter,
|
||||
FaCalendarAlt,
|
||||
FaDatabase,
|
||||
FaCheckCircle,
|
||||
FaTable,
|
||||
FaBolt,
|
||||
FaSync,
|
||||
} from 'react-icons/fa'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
const EntityManager: React.FC = () => {
|
||||
const { entities, deleteEntity, toggleEntityActiveStatus, refreshEntities, generateMigration } = useEntities()
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [filterActive, setFilterActive] = useState<'all' | 'active' | 'inactive'>('all')
|
||||
const [generatingMigration, setGeneratingMigration] = useState<string | null>(null)
|
||||
const { translate } = useLocalization()
|
||||
|
||||
// Sayfa odaklandığında varlıkları yenile
|
||||
useEffect(() => {
|
||||
const handleVisibilityChange = () => {
|
||||
if (!document.hidden) {
|
||||
refreshEntities()
|
||||
}
|
||||
}
|
||||
|
||||
// Visibility change event listener ekle
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||
}
|
||||
}, [refreshEntities])
|
||||
|
||||
const filteredEntities = entities.filter((entity) => {
|
||||
const matchesSearch =
|
||||
entity.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
entity.displayName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(entity.description || '').toLowerCase().includes(searchTerm.toLowerCase())
|
||||
|
||||
const matchesActiveFilter =
|
||||
filterActive === 'all' ||
|
||||
(filterActive === 'active' && entity.isActive) ||
|
||||
(filterActive === 'inactive' && !entity.isActive)
|
||||
|
||||
return matchesSearch && matchesActiveFilter
|
||||
})
|
||||
|
||||
const handleToggleActive = async (id: string) => {
|
||||
try {
|
||||
await toggleEntityActiveStatus(id)
|
||||
} catch (err) {
|
||||
console.error('Failed to toggle entity status:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id: string, name: string) => {
|
||||
if (
|
||||
window.confirm(
|
||||
`Are you sure you want to delete the "${name}" entity?\n\nThis action will permanently remove:\n• The entity and all its fields\n• All related migrations\n• All related API endpoints\n\nThis cannot be undone.`,
|
||||
)
|
||||
) {
|
||||
try {
|
||||
await deleteEntity(id)
|
||||
} catch (err) {
|
||||
console.error('Failed to delete entity:', err)
|
||||
alert(translate('::App.DeveloperKit.Entity.DeleteFailed'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleGenerateMigration = async (entityId: string) => {
|
||||
try {
|
||||
setGeneratingMigration(entityId)
|
||||
await generateMigration(entityId)
|
||||
alert('Migration generated successfully!')
|
||||
} catch (err) {
|
||||
console.error('Failed to generate migration:', err)
|
||||
alert('Failed to generate migration. Please try again.')
|
||||
} finally {
|
||||
setGeneratingMigration(null)
|
||||
}
|
||||
}
|
||||
|
||||
const stats = {
|
||||
total: entities.length,
|
||||
active: entities.filter((e) => e.isActive).length,
|
||||
migrationsPending: entities.filter((e) => e.migrationStatus === 'pending').length,
|
||||
endpointsApplied: entities.filter((e) => e.endpointStatus === 'applied').length,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900">
|
||||
{translate('::App.DeveloperKit.Entity')}
|
||||
</h1>
|
||||
<p className="text-slate-600">{translate('::App.DeveloperKit.Entity.Description')}</p>
|
||||
</div>
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.entitiesNew}
|
||||
className="flex items-center gap-2 bg-emerald-600 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 transition-colors shadow-sm hover:shadow-md"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
{translate('::App.DeveloperKit.Entity.NewEntity')}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Entity.TotalEntities')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">{stats.total}</p>
|
||||
</div>
|
||||
<div className="bg-blue-100 text-blue-600 p-3 rounded-lg">
|
||||
<FaDatabase className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Entity.ActiveEntities')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">
|
||||
{stats.active}
|
||||
<span className="text-sm font-normal text-slate-500 ml-1">/ {stats.total}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-green-100 text-green-600 p-3 rounded-lg">
|
||||
<FaCheckCircle className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Entity.PendingMigrations')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">{stats.migrationsPending}</p>
|
||||
</div>
|
||||
<div className="bg-purple-100 text-purple-600 p-3 rounded-lg">
|
||||
<FaBolt className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Entity.CrudEndpoints')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">{stats.endpointsApplied}</p>
|
||||
</div>
|
||||
<div className="bg-emerald-100 text-emerald-600 p-3 rounded-lg">
|
||||
<FaBolt className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col lg:flex-row gap-4">
|
||||
<div className="flex-1 relative">
|
||||
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-slate-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder={translate('::App.DeveloperKit.Entity.SearchPlaceholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<FaFilter className="w-5 h-5 text-slate-500" />
|
||||
<select
|
||||
value={filterActive}
|
||||
onChange={(e) => setFilterActive(e.target.value as 'all' | 'active' | 'inactive')}
|
||||
className="px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent transition-colors"
|
||||
>
|
||||
<option value="all">{translate('::App.DeveloperKit.Entity.Filter.All')}</option>
|
||||
<option value="active">{translate('::App.DeveloperKit.Entity.Filter.Active')}</option>
|
||||
<option value="inactive">
|
||||
{translate('::App.DeveloperKit.Entity.Filter.Inactive')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Entities List */}
|
||||
{filteredEntities.length > 0 ? (
|
||||
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
||||
{filteredEntities.map((entity) => {
|
||||
return (
|
||||
<div
|
||||
key={entity.id}
|
||||
className="bg-white rounded-lg border border-slate-200 shadow-sm hover:shadow-md transition-all duration-200 group"
|
||||
>
|
||||
<div className="p-5">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
{/* Sol taraf */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="bg-blue-100 text-blue-600 p-2 rounded-lg">
|
||||
<FaTable className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-slate-900">
|
||||
{entity.displayName}
|
||||
</h3>
|
||||
<p className="text-sm text-slate-500">
|
||||
{translate('::ListForms.ListFormEdit.DatabaseDataSourceTableName')}:{' '}
|
||||
{entity.tableName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sağ taraf */}
|
||||
<div className="flex flex-col items-end text-sm text-slate-600 gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<FaCalendarAlt className="w-3 h-3" />
|
||||
<span>
|
||||
{translate('::App.DeveloperKit.Entity.Updated')}:{' '}
|
||||
{entity.lastModificationTime
|
||||
? new Date(entity.lastModificationTime).toLocaleDateString()
|
||||
: 'Never'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<FaDatabase className="w-3 h-3" />
|
||||
<span>
|
||||
{entity.fields.length} {translate('::ListForms.ListFormEdit.TabFields')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{entity.description && (
|
||||
<p className="text-slate-600 text-sm mb-3">{entity.description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Entity Fields Preview */}
|
||||
<div className="mb-4">
|
||||
<div className="bg-slate-50 rounded-lg p-3">
|
||||
<div className="grid grid-cols-3 gap-2 text-xs">
|
||||
{entity.fields.slice(0, 4).map((field) => (
|
||||
<div key={field.id} className="flex items-center gap-2">
|
||||
<span
|
||||
className={`px-2 py-1 rounded text-xs font-medium ${
|
||||
field.type === 'string'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: field.type === 'number' || field.type === 'decimal'
|
||||
? 'bg-green-100 text-green-700'
|
||||
: field.type === 'boolean'
|
||||
? 'bg-purple-100 text-purple-700'
|
||||
: field.type === 'date'
|
||||
? 'bg-orange-100 text-orange-700'
|
||||
: 'bg-slate-100 text-slate-700'
|
||||
}`}
|
||||
>
|
||||
{field.type}
|
||||
</span>
|
||||
<span className="text-slate-600">{field.name}</span>
|
||||
{field.isRequired && <span className="text-red-500">*</span>}
|
||||
</div>
|
||||
))}
|
||||
{entity.fields.length > 4 && (
|
||||
<div className="text-slate-500 italic">
|
||||
+{entity.fields.length - 4}{' '}
|
||||
{translate('::App.DeveloperKit.Entity.More')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Migration and Endpoint Status */}
|
||||
<div className="mb-4 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-slate-700">
|
||||
{translate('::App.DeveloperKit.Entity.MigrationStatus')}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`text-xs px-2 py-1 rounded-full ${
|
||||
entity.migrationStatus === 'applied'
|
||||
? 'bg-green-100 text-green-700'
|
||||
: entity.migrationStatus === 'pending'
|
||||
? 'bg-yellow-100 text-yellow-700'
|
||||
: 'bg-red-100 text-red-700'
|
||||
}`}
|
||||
>
|
||||
{entity.migrationStatus}
|
||||
</span>
|
||||
{entity.migrationStatus === 'pending' && (
|
||||
<button
|
||||
onClick={() => handleGenerateMigration(entity.id)}
|
||||
disabled={generatingMigration === entity.id}
|
||||
className="flex items-center gap-1 px-2 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Generate Migration"
|
||||
>
|
||||
{generatingMigration === entity.id ? (
|
||||
<>
|
||||
<FaSync className="w-3 h-3 animate-spin" />
|
||||
<span>Generating...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaBolt className="w-3 h-3" />
|
||||
<span>Generate</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-slate-700">
|
||||
{translate('::App.DeveloperKit.Entity.EndpointStatus')}
|
||||
</span>
|
||||
<span
|
||||
className={`text-xs px-2 py-1 rounded-full ${
|
||||
entity.endpointStatus === 'applied'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: entity.endpointStatus === 'pending'
|
||||
? 'bg-orange-100 text-orange-700'
|
||||
: 'bg-red-100 text-red-700'
|
||||
}`}
|
||||
>
|
||||
{entity.endpointStatus}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-between pt-4 border-t border-slate-100">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => handleToggleActive(entity.id)}
|
||||
className={`flex items-center gap-1 px-3 py-1 rounded-full text-xs font-medium transition-colors ${
|
||||
entity.isActive
|
||||
? 'bg-green-100 text-green-700 hover:bg-green-200'
|
||||
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
{entity.isActive ? (
|
||||
<>
|
||||
<FaEye className="w-3 h-3" />
|
||||
{translate('::App.Status.Active')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaEyeSlash className="w-3 h-3" />
|
||||
{translate('::App.Status.Inactive')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.entitiesEdit.replace(
|
||||
':id',
|
||||
entity.id,
|
||||
)}
|
||||
className="p-2 text-slate-600 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
||||
title={translate('::App.DeveloperKit.Entity.Edit')}
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => handleDelete(entity.id, entity.displayName)}
|
||||
className="p-2 text-slate-600 hover:text-red-600 hover:bg-red-50 rounded transition-colors"
|
||||
title={translate('::App.DeveloperKit.Entity.Delete')}
|
||||
>
|
||||
<FaTrashAlt className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<div className="max-w-md mx-auto">
|
||||
<div className="bg-slate-100 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
|
||||
<FaDatabase className="w-8 h-8 text-slate-500" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-slate-900 mb-2">
|
||||
{searchTerm || filterActive !== 'all'
|
||||
? translate('::App.DeveloperKit.Entity.NoEntitiesFound')
|
||||
: translate('::App.DeveloperKit.Entity.NoEntitiesYet')}
|
||||
</h3>
|
||||
<p className="text-slate-600 mb-6">
|
||||
{searchTerm || filterActive !== 'all'
|
||||
? translate('::App.DeveloperKit.EmptyFilteredDescription')
|
||||
: translate('::App.DeveloperKit.Entity.CreateFirstEntity')}
|
||||
</p>
|
||||
{!searchTerm && filterActive === 'all' && (
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.entitiesNew}
|
||||
className="inline-flex items-center gap-2 bg-emerald-600 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 transition-colors shadow-sm hover:shadow-md"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
{translate('::App.DeveloperKit.Entity.CreateEntity')}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EntityManager
|
||||
|
|
@ -1,533 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useEntities } from '../../contexts/EntityContext'
|
||||
import {
|
||||
FaBolt,
|
||||
FaSearch,
|
||||
FaFilter,
|
||||
FaCalendarAlt,
|
||||
FaDatabase,
|
||||
FaPlay,
|
||||
FaCheckCircle,
|
||||
FaRegBell,
|
||||
FaRegClock,
|
||||
FaRegFileAlt,
|
||||
FaDownload,
|
||||
FaEye,
|
||||
FaSync,
|
||||
FaTrashAlt,
|
||||
FaPlus,
|
||||
} from 'react-icons/fa'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
const MigrationManager: React.FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const {
|
||||
entities,
|
||||
migrations,
|
||||
generateMigration,
|
||||
applyMigration,
|
||||
deleteMigration,
|
||||
generateCrudEndpoints,
|
||||
loading,
|
||||
error,
|
||||
} = useEntities()
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [filterStatus, setFilterStatus] = useState<'all' | 'pending' | 'applied' | 'failed'>('all')
|
||||
const [selectedMigration, setSelectedMigration] = useState<string | null>(null)
|
||||
const [applyingMigration, setApplyingMigration] = useState<string | null>(null)
|
||||
const [deletingMigration, setDeletingMigration] = useState<string | null>(null)
|
||||
const [generatingEndpoints, setGeneratingEndpoints] = useState<string | null>(null)
|
||||
const { translate } = useLocalization()
|
||||
|
||||
const filteredMigrations = migrations.filter((migration) => {
|
||||
const matchesSearch =
|
||||
migration.entityName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(migration.fileName && migration.fileName.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||
|
||||
const matchesStatusFilter = filterStatus === 'all' || migration.status === filterStatus
|
||||
|
||||
return matchesSearch && matchesStatusFilter
|
||||
})
|
||||
|
||||
const handleGenerateMigration = async (entityId: string) => {
|
||||
try {
|
||||
await generateMigration(entityId)
|
||||
} catch (error) {
|
||||
console.error('Failed to generate migration:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleApplyMigration = async (migrationId: string) => {
|
||||
try {
|
||||
setApplyingMigration(migrationId)
|
||||
await applyMigration(migrationId)
|
||||
} catch (error) {
|
||||
console.error('Failed to apply migration:', error)
|
||||
} finally {
|
||||
setApplyingMigration(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteMigration = async (migrationId: string) => {
|
||||
try {
|
||||
setDeletingMigration(migrationId)
|
||||
await deleteMigration(migrationId)
|
||||
} catch (error) {
|
||||
console.error('Failed to delete migration:', error)
|
||||
} finally {
|
||||
setDeletingMigration(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownloadMigration = (migration: (typeof migrations)[0]) => {
|
||||
if (!migration.sqlScript) return
|
||||
|
||||
const blob = new Blob([migration.sqlScript], { type: 'text/sql' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download =
|
||||
migration.fileName ||
|
||||
`migration_${migration.entityName}_${new Date().toISOString().split('T')[0]}.sql`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
const handleGenerateCrudEndpoints = async (entityId: string) => {
|
||||
try {
|
||||
setGeneratingEndpoints(entityId)
|
||||
await generateCrudEndpoints(entityId)
|
||||
// Navigate to API Endpoints page after successful generation
|
||||
navigate(ROUTES_ENUM.protected.saas.developerKit.migrations)
|
||||
} catch (error) {
|
||||
console.error('Failed to generate CRUD endpoints:', error)
|
||||
} finally {
|
||||
setGeneratingEndpoints(null)
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 'bg-yellow-100 text-yellow-800 border-yellow-200'
|
||||
case 'applied':
|
||||
return 'bg-green-100 text-green-800 border-green-200'
|
||||
case 'failed':
|
||||
return 'bg-red-100 text-red-800 border-red-200'
|
||||
default:
|
||||
return 'bg-slate-100 text-slate-800 border-slate-200'
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return FaRegClock
|
||||
case 'applied':
|
||||
return FaCheckCircle
|
||||
case 'failed':
|
||||
return FaRegBell
|
||||
default:
|
||||
return FaRegClock
|
||||
}
|
||||
}
|
||||
|
||||
const stats = {
|
||||
total: migrations.length,
|
||||
pending: migrations.filter((m) => m.status === 'pending').length,
|
||||
applied: migrations.filter((m) => m.status === 'applied').length,
|
||||
failed: migrations.filter((m) => m.status === 'failed').length,
|
||||
}
|
||||
|
||||
const entitiesNeedingMigration = entities.filter(
|
||||
(e) => e.migrationStatus === 'pending' || !e.migrationId,
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Error Message */}
|
||||
{error && (
|
||||
<div className="mb-6 bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 text-red-800">
|
||||
<FaRegBell className="w-5 h-5" />
|
||||
<span className="font-medium">Error</span>
|
||||
</div>
|
||||
<p className="text-red-700 text-sm mt-1">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900">
|
||||
{translate('::App.DeveloperKit.Migrations')}
|
||||
</h1>
|
||||
<p className="text-slate-600">{translate('::App.DeveloperKit.Migration.Description')}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 bg-blue-100 text-blue-700 px-3 py-1 rounded-full text-sm font-medium">
|
||||
<FaDatabase className="w-4 h-4" />
|
||||
{translate('::App.DeveloperKit.Migration.SchemaManagement')}
|
||||
</div>
|
||||
{loading && (
|
||||
<div className="flex items-center gap-2 bg-yellow-100 text-yellow-700 px-3 py-1 rounded-full text-sm font-medium">
|
||||
<FaSync className="w-4 h-4 animate-spin" />
|
||||
{translate('::App.Loading')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Migration.Total')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">{stats.total}</p>
|
||||
</div>
|
||||
<div className="bg-blue-100 text-blue-600 p-3 rounded-lg">
|
||||
<FaRegFileAlt className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Migration.Pending')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">{stats.pending}</p>
|
||||
</div>
|
||||
<div className="bg-yellow-100 text-yellow-600 p-3 rounded-lg">
|
||||
<FaRegClock className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Migration.Applied')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">{stats.applied}</p>
|
||||
</div>
|
||||
<div className="bg-green-100 text-green-600 p-3 rounded-lg">
|
||||
<FaCheckCircle className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600">
|
||||
{translate('::App.DeveloperKit.Migration.Failed')}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1">{stats.failed}</p>
|
||||
</div>
|
||||
<div className="bg-red-100 text-red-600 p-3 rounded-lg">
|
||||
<FaRegBell className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Entities Needing Migration */}
|
||||
{entitiesNeedingMigration.length > 0 && (
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="bg-yellow-100 rounded-lg p-2">
|
||||
<FaBolt className="w-6 h-6 text-yellow-600" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-slate-900">
|
||||
{translate('::App.DeveloperKit.Migration.EntitiesRequireMigration')}
|
||||
</h3>
|
||||
<p className="text-slate-700 mb-2">
|
||||
{translate('::App.DeveloperKit.Migration.EntitiesRequireMigrationDescription')}
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-3">
|
||||
{entitiesNeedingMigration.map((entity) => (
|
||||
<div key={entity.id} className="bg-white border border-yellow-300 rounded-lg p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-medium text-slate-900">{entity.displayName}</h4>
|
||||
<p className="text-sm text-slate-600">
|
||||
{entity.fields.length} {translate('::ListForms.ListFormEdit.TabFields')}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleGenerateMigration(entity.id)}
|
||||
disabled={loading}
|
||||
className="bg-yellow-600 text-white px-3 py-1 rounded text-sm hover:bg-yellow-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading
|
||||
? translate('::App.DeveloperKit.Migration.Generating')
|
||||
: translate('::Create')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col lg:flex-row gap-4">
|
||||
<div className="flex-1 relative">
|
||||
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-slate-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder={translate('::App.DeveloperKit.Migration.SearchPlaceholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<FaFilter className="w-5 h-5 text-slate-500" />
|
||||
<select
|
||||
value={filterStatus}
|
||||
onChange={(e) =>
|
||||
setFilterStatus(e.target.value as 'all' | 'pending' | 'applied' | 'failed')
|
||||
}
|
||||
className="px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
|
||||
>
|
||||
<option value="all">{translate('::App.DeveloperKit.Migration.AllStatus')}</option>
|
||||
<option value="pending">Pending</option>
|
||||
<option value="applied">Applied</option>
|
||||
<option value="failed">Failed</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Migrations List */}
|
||||
{filteredMigrations.length > 0 ? (
|
||||
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
||||
{filteredMigrations.map((migration) => {
|
||||
const StatusIcon = getStatusIcon(migration.status)
|
||||
return (
|
||||
<div
|
||||
key={migration.id}
|
||||
className="bg-white rounded-lg border border-slate-200 shadow-sm"
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
{/* Sol taraf */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="bg-blue-100 text-blue-600 p-2 rounded-lg">
|
||||
<FaDatabase className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-slate-900">
|
||||
{migration.fileName || `Migration for ${migration.entityName}`}
|
||||
</h3>
|
||||
<p className="text-sm text-slate-600">Entity: {migration.entityName}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sağ taraf */}
|
||||
<div className="flex flex-col items-end gap-2 text-right">
|
||||
{/* Status etiketi */}
|
||||
<span
|
||||
className={`px-3 py-1 text-sm font-medium rounded-full border ${getStatusColor(
|
||||
migration.status,
|
||||
)}`}
|
||||
>
|
||||
<StatusIcon className="w-4 h-4 inline mr-1" />
|
||||
{migration.status.charAt(0).toUpperCase() + migration.status.slice(1)}
|
||||
</span>
|
||||
|
||||
{/* Tarihler */}
|
||||
<div className="flex flex-col items-end text-xs text-slate-500">
|
||||
<div className="flex items-center gap-1">
|
||||
<FaCalendarAlt className="w-3 h-3" />
|
||||
<span>
|
||||
{translate('::App.DeveloperKit.Migration.Created')}{' '}
|
||||
{new Date(migration.creationTime).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
{migration.appliedAt && (
|
||||
<div className="flex items-center gap-1">
|
||||
<FaCheckCircle className="w-3 h-3" />
|
||||
<span>
|
||||
{translate('::App.DeveloperKit.Migration.Applied')}{' '}
|
||||
{new Date(migration.appliedAt).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-between pt-4 border-t border-slate-100">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() =>
|
||||
setSelectedMigration(
|
||||
selectedMigration === migration.id ? null : migration.id,
|
||||
)
|
||||
}
|
||||
className={`flex items-center gap-1 px-3 py-1 text-sm rounded transition-colors ${
|
||||
migration.sqlScript
|
||||
? 'text-slate-600 hover:text-slate-900 hover:bg-slate-100'
|
||||
: 'text-orange-600 hover:text-orange-900 hover:bg-orange-50'
|
||||
}`}
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
{translate('::App.DeveloperKit.Migration.ViewSQL')}
|
||||
{!migration.sqlScript && (
|
||||
<span className="ml-1 text-xs text-orange-500">
|
||||
({translate('::App.DeveloperKit.Migration.NoSQL')})
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDownloadMigration(migration)}
|
||||
disabled={!migration.sqlScript}
|
||||
className={`flex items-center gap-1 px-3 py-1 text-sm rounded transition-colors ${
|
||||
migration.sqlScript
|
||||
? 'text-slate-600 hover:text-slate-900 hover:bg-slate-100'
|
||||
: 'text-slate-400 cursor-not-allowed'
|
||||
}`}
|
||||
>
|
||||
<FaDownload className="w-4 h-4" />
|
||||
{translate('::App.DeveloperKit.Migration.Download')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{migration.status === 'pending' && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => handleApplyMigration(migration.id)}
|
||||
disabled={applyingMigration === migration.id || loading}
|
||||
className="flex items-center gap-2 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{applyingMigration === migration.id ? (
|
||||
<>
|
||||
<FaSync className="w-4 h-4 animate-spin" />
|
||||
{translate('::App.DeveloperKit.Migration.Applying')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaPlay className="w-4 h-4" />
|
||||
{translate('::App.DeveloperKit.Migration.ApplyMigration')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteMigration(migration.id)}
|
||||
disabled={deletingMigration === migration.id || loading}
|
||||
className="flex items-center gap-2 bg-red-600 text-white px-3 py-2 rounded-lg hover:bg-red-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{deletingMigration === migration.id ? (
|
||||
<FaSync className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<FaTrashAlt className="w-4 h-4" />
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{migration.status === 'applied' && (
|
||||
<button
|
||||
onClick={() => handleGenerateCrudEndpoints(migration.entityId)}
|
||||
disabled={generatingEndpoints === migration.entityId || loading}
|
||||
className="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{generatingEndpoints === migration.entityId ? (
|
||||
<>
|
||||
<FaSync className="w-4 h-4 animate-spin" />
|
||||
{translate('::App.DeveloperKit.Migration.Generating')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
{translate('::App.DeveloperKit.Migration.GenerateCrudEndpoints')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SQL Script Preview */}
|
||||
{selectedMigration === migration.id && (
|
||||
<div className="mt-4 pt-4 border-t border-slate-200">
|
||||
<h4 className="font-medium text-slate-900 mb-2">
|
||||
{translate('::ListForms.ListFormEdit.SqlQuery')}
|
||||
</h4>
|
||||
{migration.sqlScript ? (
|
||||
<div className="bg-slate-900 rounded-lg p-4 overflow-x-auto">
|
||||
<pre className="text-green-400 text-sm">
|
||||
<code>{migration.sqlScript}</code>
|
||||
</pre>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 text-yellow-800">
|
||||
<FaRegBell className="w-5 h-5" />
|
||||
<span className="font-medium">
|
||||
{translate('::App.DeveloperKit.Migration.NoSQLScript')}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-yellow-700 text-sm mt-1">
|
||||
{translate('::App.DeveloperKit.Migration.NoSQLScriptDesc')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error Message */}
|
||||
{migration.status === 'failed' && migration.errorMessage && (
|
||||
<div className="mt-4 pt-4 border-t border-slate-200">
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 text-red-800 mb-2">
|
||||
<FaRegBell className="w-5 h-5" />
|
||||
<span className="font-medium">
|
||||
{translate('::App.DeveloperKit.Migration.MigrationFailed')}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-red-700 text-sm">{migration.errorMessage}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<div className="max-w-md mx-auto">
|
||||
<div className="bg-slate-100 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
|
||||
<FaBolt className="w-8 h-8 text-slate-500" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-slate-900 mb-2">
|
||||
{searchTerm || filterStatus !== 'all'
|
||||
? translate('::App.DeveloperKit.Migration.NoMigrationsFound')
|
||||
: translate('::App.DeveloperKit.Migration.NoMigrationsYet')}
|
||||
</h3>
|
||||
<p className="text-slate-600">
|
||||
{searchTerm || filterStatus !== 'all'
|
||||
? translate('::App.DeveloperKit.EmptyFilteredDescription')
|
||||
: translate('::App.DeveloperKit.Migration.CreateEntitiesForMigration')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MigrationManager
|
||||
|
|
@ -15,24 +15,12 @@ const DeveloperLayout: React.FC<DeveloperLayoutProps> = ({ children }) => {
|
|||
const navigate = useNavigate()
|
||||
|
||||
const navigation = [
|
||||
{
|
||||
id: 'dashboard',
|
||||
label: translate('::App.DeveloperKit.Dashboard'),
|
||||
icon: FaTachometerAlt,
|
||||
path: ROUTES_ENUM.protected.saas.developerKit.dashboard,
|
||||
},
|
||||
{
|
||||
id: 'entities',
|
||||
label: translate('::App.DeveloperKit.Entity'),
|
||||
icon: FaDatabase,
|
||||
path: ROUTES_ENUM.protected.saas.developerKit.entities,
|
||||
},
|
||||
{
|
||||
id: 'migrations',
|
||||
label: translate('::App.DeveloperKit.Migrations'),
|
||||
icon: FaBolt,
|
||||
path: ROUTES_ENUM.protected.saas.developerKit.migrations,
|
||||
},
|
||||
{
|
||||
id: 'endpoints',
|
||||
label: translate('::App.DeveloperKit.CrudEndpoints'),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import React, { createContext, useContext, useState, useEffect } from "react";
|
||||
import { developerKitService } from "@/services/developerKit.service";
|
||||
import {
|
||||
developerKitService,
|
||||
} from "@/services/developerKit.service";
|
||||
import { CrudEndpoint, ApiMigration, CreateUpdateCustomEntityDto, CustomEntity } from "@/proxy/developerKit/models";
|
||||
CrudEndpoint,
|
||||
CreateUpdateSqlTableDto,
|
||||
SqlTable,
|
||||
} from "@/proxy/developerKit/models";
|
||||
|
||||
export const FIELD_TYPE_OPTIONS = [
|
||||
{ label: "String", value: "string" },
|
||||
|
|
@ -16,23 +18,17 @@ export const FIELD_TYPE_OPTIONS = [
|
|||
export type EntityFieldType = (typeof FIELD_TYPE_OPTIONS)[number]["value"];
|
||||
|
||||
interface EntityContextType {
|
||||
entities: CustomEntity[];
|
||||
migrations: ApiMigration[];
|
||||
entities: SqlTable[];
|
||||
generatedEndpoints: CrudEndpoint[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
// Entity operations
|
||||
addEntity: (entity: CreateUpdateCustomEntityDto) => Promise<void>;
|
||||
updateEntity: (id: string, entity: CreateUpdateCustomEntityDto) => Promise<void>;
|
||||
addEntity: (entity: CreateUpdateSqlTableDto) => Promise<void>;
|
||||
updateEntity: (id: string, entity: CreateUpdateSqlTableDto) => Promise<void>;
|
||||
deleteEntity: (id: string) => Promise<void>;
|
||||
getEntity: (id: string) => CustomEntity | undefined;
|
||||
getEntity: (id: string) => SqlTable | undefined;
|
||||
refreshEntities: () => Promise<void>;
|
||||
toggleEntityActiveStatus: (id: string) => Promise<void>;
|
||||
// Migration operations
|
||||
generateMigration: (entityId: string) => Promise<void>;
|
||||
applyMigration: (migrationId: string) => Promise<void>;
|
||||
getEntityMigrations: (entityId: string) => ApiMigration[];
|
||||
deleteMigration: (id: string) => Promise<void>;
|
||||
// Generated endpoint operations
|
||||
generateCrudEndpoints: (entityId: string) => Promise<void>;
|
||||
toggleEndpoint: (endpointId: string) => Promise<void>;
|
||||
|
|
@ -51,28 +47,21 @@ export const useEntities = () => {
|
|||
return context;
|
||||
};
|
||||
|
||||
export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [entities, setEntities] = useState<CustomEntity[]>([]);
|
||||
export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [entities, setEntities] = useState<SqlTable[]>([]);
|
||||
const [generatedEndpoints, setGeneratedEndpoints] = useState<CrudEndpoint[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [migrations, setMigrations] = useState<ApiMigration[]>([]);
|
||||
const [generatedEndpoints, setGeneratedEndpoints] = useState<CrudEndpoint[]>([]);
|
||||
|
||||
const refreshEntities = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const [entitiesData, migrationsData, generatedEndpointsData] =
|
||||
await Promise.all([
|
||||
developerKitService.getCustomEntities(),
|
||||
developerKitService.getMigrations(),
|
||||
developerKitService.getGeneratedEndpoints(),
|
||||
]);
|
||||
const [entitiesData, generatedEndpointsData] = await Promise.all([
|
||||
developerKitService.getSqlTables(),
|
||||
developerKitService.getGeneratedEndpoints(),
|
||||
]);
|
||||
setEntities(entitiesData.items || []);
|
||||
setMigrations(migrationsData.items || []);
|
||||
setGeneratedEndpoints(generatedEndpointsData.items || []);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to fetch data");
|
||||
|
|
@ -86,11 +75,11 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||
refreshEntities();
|
||||
}, []);
|
||||
|
||||
const addEntity = async (entityData: CreateUpdateCustomEntityDto) => {
|
||||
const addEntity = async (entityData: CreateUpdateSqlTableDto) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const newEntity = await developerKitService.createCustomEntity(entityData);
|
||||
const newEntity = await developerKitService.createSqlTable(entityData);
|
||||
setEntities((prev) => [...prev, newEntity]);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to create entity");
|
||||
|
|
@ -100,21 +89,12 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const updateEntity = async (
|
||||
id: string,
|
||||
entityData: CreateUpdateCustomEntityDto
|
||||
) => {
|
||||
const updateEntity = async (id: string, entityData: CreateUpdateSqlTableDto) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const updatedEntity = await developerKitService.updateCustomEntity(id, entityData);
|
||||
|
||||
// State'i güncelle
|
||||
setEntities((prev) =>
|
||||
prev.map((entity) => (entity.id === id ? updatedEntity : entity))
|
||||
);
|
||||
|
||||
// Güvenlik için tüm varlıkları yenile
|
||||
const updatedEntity = await developerKitService.updateSqlTable(id, entityData);
|
||||
setEntities((prev) => prev.map((e) => (e.id === id ? updatedEntity : e)));
|
||||
await refreshEntities();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to update entity");
|
||||
|
|
@ -128,16 +108,9 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
await developerKitService.deleteCustomEntity(id);
|
||||
|
||||
// Remove entity from state
|
||||
setEntities((prev) => prev.filter((entity) => entity.id !== id));
|
||||
|
||||
// Remove related migrations from state
|
||||
setMigrations((prev) => prev.filter((migration) => migration.entityId !== id));
|
||||
|
||||
// Remove related endpoints from state
|
||||
setGeneratedEndpoints((prev) => prev.filter((endpoint) => endpoint.entityId !== id));
|
||||
await developerKitService.deleteSqlTable(id);
|
||||
setEntities((prev) => prev.filter((e) => e.id !== id));
|
||||
setGeneratedEndpoints((prev) => prev.filter((ep) => ep.entityId !== id));
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to delete entity");
|
||||
throw err;
|
||||
|
|
@ -150,88 +123,28 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
await developerKitService.toggleCustomEntityActiveStatus(id);
|
||||
// Refresh entities to get updated status
|
||||
await developerKitService.toggleSqlTableActiveStatus(id);
|
||||
await refreshEntities();
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof Error ? err.message : "Failed to toggle entity status"
|
||||
);
|
||||
setError(err instanceof Error ? err.message : "Failed to toggle entity status");
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getEntity = (id: string): CustomEntity | undefined => {
|
||||
return entities.find((entity) => entity.id === id);
|
||||
};
|
||||
const getEntity = (id: string): SqlTable | undefined =>
|
||||
entities.find((e) => e.id === id);
|
||||
|
||||
// Migration operations
|
||||
const generateMigration = async (entityId: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const migration = await developerKitService.generateMigration(entityId);
|
||||
setMigrations((prev) => [...prev, migration]);
|
||||
// Refresh entities to get updated migration status
|
||||
await refreshEntities();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to generate migration");
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const applyMigration = async (migrationId: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const appliedMigration = await developerKitService.applyMigration(migrationId);
|
||||
setMigrations((prev) =>
|
||||
prev.map((m) => (m.id === migrationId ? appliedMigration : m))
|
||||
);
|
||||
// Refresh entities to get updated migration status
|
||||
await refreshEntities();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to apply migration");
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getEntityMigrations = (entityId: string): ApiMigration[] => {
|
||||
return migrations.filter((migration) => migration.entityId === entityId);
|
||||
};
|
||||
|
||||
const deleteMigration = async (id: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
await developerKitService.deleteMigration(id);
|
||||
setMigrations((prev) => prev.filter((migration) => migration.id !== id));
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to delete migration");
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Generated endpoint operations
|
||||
const generateCrudEndpoints = async (entityId: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const endpointsData = await developerKitService.generateCrudEndpoints(entityId);
|
||||
// Replace existing endpoints for this entity
|
||||
setGeneratedEndpoints((prev) => [
|
||||
...prev.filter((e) => e.entityId !== entityId),
|
||||
...endpointsData.items || [],
|
||||
...(endpointsData.items || []),
|
||||
]);
|
||||
// Refresh entities to get updated endpoint status
|
||||
await refreshEntities();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to generate CRUD endpoints");
|
||||
|
|
@ -247,7 +160,7 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||
setError(null);
|
||||
const updatedEndpoint = await developerKitService.toggleGeneratedEndpoint(endpointId);
|
||||
setGeneratedEndpoints((prev) =>
|
||||
prev.map((e) => (e.id === endpointId ? updatedEndpoint : e))
|
||||
prev.map((e) => (e.id === endpointId ? updatedEndpoint : e)),
|
||||
);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to toggle endpoint");
|
||||
|
|
@ -257,16 +170,15 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const getEntityEndpoints = (entityId: string): CrudEndpoint[] => {
|
||||
return generatedEndpoints.filter((endpoint) => endpoint.entityId === entityId);
|
||||
};
|
||||
const getEntityEndpoints = (entityId: string): CrudEndpoint[] =>
|
||||
generatedEndpoints.filter((e) => e.entityId === entityId);
|
||||
|
||||
const deleteGeneratedEndpoint = async (id: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
await developerKitService.deleteGeneratedEndpoint(id);
|
||||
setGeneratedEndpoints((prev) => prev.filter((endpoint) => endpoint.id !== id));
|
||||
setGeneratedEndpoints((prev) => prev.filter((e) => e.id !== id));
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to delete generated endpoint");
|
||||
throw err;
|
||||
|
|
@ -279,7 +191,6 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||
<EntityContext.Provider
|
||||
value={{
|
||||
entities,
|
||||
migrations,
|
||||
generatedEndpoints,
|
||||
loading,
|
||||
error,
|
||||
|
|
@ -289,10 +200,6 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||
getEntity,
|
||||
refreshEntities,
|
||||
toggleEntityActiveStatus,
|
||||
generateMigration,
|
||||
applyMigration,
|
||||
getEntityMigrations,
|
||||
deleteMigration,
|
||||
generateCrudEndpoints,
|
||||
toggleEndpoint,
|
||||
getEntityEndpoints,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export interface CustomEntity {
|
||||
export interface SqlTable {
|
||||
id: string;
|
||||
menu: string;
|
||||
name: string;
|
||||
|
|
@ -8,15 +8,13 @@ export interface CustomEntity {
|
|||
isActive: boolean;
|
||||
isFullAuditedEntity: boolean;
|
||||
isMultiTenant: boolean;
|
||||
migrationStatus: "pending" | "applied" | "failed";
|
||||
endpointStatus: "pending" | "applied" | "failed";
|
||||
migrationId?: string;
|
||||
fields: CustomEntityField[];
|
||||
fields: SqlTableField[];
|
||||
creationTime: string;
|
||||
lastModificationTime?: string;
|
||||
}
|
||||
|
||||
export interface CustomEntityField {
|
||||
export interface SqlTableField {
|
||||
id: string;
|
||||
entityId: string;
|
||||
name: string;
|
||||
|
|
@ -31,7 +29,7 @@ export interface CustomEntityField {
|
|||
displayOrder: number;
|
||||
}
|
||||
|
||||
export interface CreateUpdateCustomEntityFieldDto {
|
||||
export interface CreateUpdateSqlTableFieldDto {
|
||||
id?: string;
|
||||
name: string;
|
||||
type: "string" | "number" | "boolean" | "date" | "guid" | "decimal";
|
||||
|
|
@ -43,7 +41,7 @@ export interface CreateUpdateCustomEntityFieldDto {
|
|||
displayOrder: number;
|
||||
}
|
||||
|
||||
export interface CreateUpdateCustomEntityDto {
|
||||
export interface CreateUpdateSqlTableDto {
|
||||
menu: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
|
|
@ -52,7 +50,7 @@ export interface CreateUpdateCustomEntityDto {
|
|||
isActive: boolean;
|
||||
isFullAuditedEntity: boolean;
|
||||
isMultiTenant: boolean;
|
||||
fields: CreateUpdateCustomEntityFieldDto[];
|
||||
fields: CreateUpdateSqlTableFieldDto[];
|
||||
}
|
||||
|
||||
export interface CrudEndpoint {
|
||||
|
|
@ -109,26 +107,4 @@ export interface CreateUpdateCustomComponentDto {
|
|||
description?: string;
|
||||
isActive: boolean;
|
||||
dependencies?: string;
|
||||
}
|
||||
|
||||
export interface ApiMigration {
|
||||
id: string;
|
||||
entityId: string;
|
||||
entityName: string;
|
||||
fileName?: string;
|
||||
sqlScript?: string;
|
||||
status: "pending" | "applied" | "failed";
|
||||
creationTime: string;
|
||||
lastModificationTime?: string;
|
||||
appliedAt?: string;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export interface CreateUpdateApiMigrationDto {
|
||||
entityId: string;
|
||||
entityName: string;
|
||||
fileName?: string;
|
||||
sqlScript?: string;
|
||||
status?: "pending" | "applied" | "failed";
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
|
@ -36,7 +36,6 @@ export const ROUTES_ENUM = {
|
|||
entities: '/admin/developerkit/entities',
|
||||
entitiesNew: '/admin/developerkit/entities/new',
|
||||
entitiesEdit: '/admin/developerkit/entities/edit/:id',
|
||||
migrations: '/admin/developerkit/migrations',
|
||||
endpoints: '/admin/developerkit/endpoints',
|
||||
endpointsNew: '/admin/developerkit/endpoints/new',
|
||||
components: '/admin/developerkit/components',
|
||||
|
|
|
|||
|
|
@ -2,69 +2,67 @@ import { PagedResultDto } from '@/proxy'
|
|||
import apiService from './api.service'
|
||||
import {
|
||||
CrudEndpoint,
|
||||
ApiMigration,
|
||||
CreateUpdateCrudEndpointDto,
|
||||
CreateUpdateApiMigrationDto,
|
||||
CreateUpdateCustomComponentDto,
|
||||
CreateUpdateCustomEntityDto,
|
||||
CreateUpdateSqlTableDto,
|
||||
CustomComponent,
|
||||
CustomComponentDto,
|
||||
CustomEntity,
|
||||
SqlTable,
|
||||
} from '@/proxy/developerKit/models'
|
||||
|
||||
class DeveloperKitService {
|
||||
async getCustomEntities(): Promise<PagedResultDto<CustomEntity>> {
|
||||
const response = await apiService.fetchData<PagedResultDto<CustomEntity>>({
|
||||
url: '/api/app/custom-entity',
|
||||
async getSqlTables(): Promise<PagedResultDto<SqlTable>> {
|
||||
const response = await apiService.fetchData<PagedResultDto<SqlTable>>({
|
||||
url: '/api/app/sql-table',
|
||||
method: 'GET',
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async getActiveCustomEntities(): Promise<PagedResultDto<CustomEntity>> {
|
||||
const response = await apiService.fetchData<PagedResultDto<CustomEntity>>({
|
||||
url: '/api/app/custom-entity/active-entities',
|
||||
async getActiveSqlTables(): Promise<PagedResultDto<SqlTable>> {
|
||||
const response = await apiService.fetchData<PagedResultDto<SqlTable>>({
|
||||
url: '/api/app/sql-table/active-tables',
|
||||
method: 'GET',
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async getCustomEntity(id: string): Promise<CustomEntity> {
|
||||
const response = await apiService.fetchData<CustomEntity>({
|
||||
url: `/api/app/custom-entity/${id}`,
|
||||
async getSqlTable(id: string): Promise<SqlTable> {
|
||||
const response = await apiService.fetchData<SqlTable>({
|
||||
url: `/api/app/sql-table/${id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async createCustomEntity(data: CreateUpdateCustomEntityDto): Promise<CustomEntity> {
|
||||
const response = await apiService.fetchData<CustomEntity>({
|
||||
url: '/api/app/custom-entity',
|
||||
async createSqlTable(data: CreateUpdateSqlTableDto): Promise<SqlTable> {
|
||||
const response = await apiService.fetchData<SqlTable>({
|
||||
url: '/api/app/sql-table',
|
||||
method: 'POST',
|
||||
data: data as any,
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async updateCustomEntity(id: string, entity: CreateUpdateCustomEntityDto): Promise<CustomEntity> {
|
||||
const response = await apiService.fetchData<CustomEntity>({
|
||||
url: `/api/app/custom-entity/${id}`,
|
||||
async updateSqlTable(id: string, entity: CreateUpdateSqlTableDto): Promise<SqlTable> {
|
||||
const response = await apiService.fetchData<SqlTable>({
|
||||
url: `/api/app/sql-table/${id}`,
|
||||
method: 'PUT',
|
||||
data: entity as any,
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async deleteCustomEntity(id: string): Promise<void> {
|
||||
async deleteSqlTable(id: string): Promise<void> {
|
||||
await apiService.fetchData<void>({
|
||||
url: `/api/app/custom-entity/${id}`,
|
||||
url: `/api/app/sql-table/${id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
async toggleCustomEntityActiveStatus(id: string): Promise<void> {
|
||||
async toggleSqlTableActiveStatus(id: string): Promise<void> {
|
||||
await apiService.fetchData<void>({
|
||||
url: `/api/app/custom-entity/${id}/toggle-active-status`,
|
||||
url: `/api/app/sql-table/${id}/toggle-active-status`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
|
@ -122,72 +120,6 @@ class DeveloperKitService {
|
|||
})
|
||||
}
|
||||
|
||||
// Migration endpoints
|
||||
async getMigrations(): Promise<PagedResultDto<ApiMigration>> {
|
||||
const response = await apiService.fetchData<PagedResultDto<ApiMigration>>({
|
||||
url: '/api/app/crud-migration',
|
||||
method: 'GET',
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async getMigration(id: string): Promise<ApiMigration> {
|
||||
const response = await apiService.fetchData<ApiMigration>({
|
||||
url: `/api/app/crud-migration/${id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async createMigration(data: CreateUpdateApiMigrationDto): Promise<ApiMigration> {
|
||||
const response = await apiService.fetchData<ApiMigration>({
|
||||
url: '/api/app/crud-migration',
|
||||
method: 'POST',
|
||||
data: data as any,
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async updateMigration(id: string, data: CreateUpdateApiMigrationDto): Promise<ApiMigration> {
|
||||
const response = await apiService.fetchData<ApiMigration>({
|
||||
url: `/api/app/crud-migration/${id}`,
|
||||
method: 'PUT',
|
||||
data: data as any,
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async deleteMigration(id: string): Promise<void> {
|
||||
await apiService.fetchData<void>({
|
||||
url: `/api/app/crud-migration/${id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
async applyMigration(id: string): Promise<ApiMigration> {
|
||||
const response = await apiService.fetchData<ApiMigration>({
|
||||
url: `/api/app/crud-migration/${id}/apply-migration`,
|
||||
method: 'POST',
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async getPendingMigrations(): Promise<PagedResultDto<ApiMigration>> {
|
||||
const response = await apiService.fetchData<PagedResultDto<ApiMigration>>({
|
||||
url: '/api/app/crud-migration/pending-migrations',
|
||||
method: 'GET',
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async generateMigration(entityId: string): Promise<ApiMigration> {
|
||||
const response = await apiService.fetchData<ApiMigration>({
|
||||
url: `/api/app/crud-migration/generate-migration/${entityId}`,
|
||||
method: 'POST',
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
// Generated Endpoint endpoints
|
||||
async getGeneratedEndpoints(): Promise<PagedResultDto<CrudEndpoint>> {
|
||||
const response = await apiService.fetchData<PagedResultDto<CrudEndpoint>>({
|
||||
|
|
|
|||
|
|
@ -1,128 +0,0 @@
|
|||
import { ApiMigration, CrudEndpoint, CustomEntity } from '@/proxy/developerKit/models';
|
||||
import { developerKitService } from '@/services/developerKit.service';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export const useEntities = () => {
|
||||
const [entities, setEntities] = useState<CustomEntity[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchEntities = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await developerKitService.getCustomEntities();
|
||||
setEntities(data.items || []);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch entities');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchEntities();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
entities,
|
||||
loading,
|
||||
error,
|
||||
refetch: fetchEntities,
|
||||
};
|
||||
};
|
||||
|
||||
export const useMigrations = () => {
|
||||
const [migrations, setMigrations] = useState<ApiMigration[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchMigrations = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await developerKitService.getMigrations();
|
||||
setMigrations(data.items || []);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch migrations');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchMigrations();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
migrations,
|
||||
loading,
|
||||
error,
|
||||
refetch: fetchMigrations,
|
||||
};
|
||||
};
|
||||
|
||||
export const useGeneratedEndpoints = () => {
|
||||
const [endpoints, setEndpoints] = useState<CrudEndpoint[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchEndpoints = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await developerKitService.getGeneratedEndpoints();
|
||||
setEndpoints(data.items || []);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch endpoints');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchEndpoints();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
endpoints,
|
||||
loading,
|
||||
error,
|
||||
refetch: fetchEndpoints,
|
||||
};
|
||||
};
|
||||
|
||||
// System health durumunu kontrol eden hook
|
||||
export const useSystemHealth = () => {
|
||||
const [isOnline, setIsOnline] = useState(true);
|
||||
const [lastCheck, setLastCheck] = useState<Date>(new Date());
|
||||
|
||||
const checkSystemHealth = async () => {
|
||||
try {
|
||||
// API'ye basit bir health check yapalım
|
||||
await developerKitService.getCustomEntities();
|
||||
setIsOnline(true);
|
||||
setLastCheck(new Date());
|
||||
} catch (error) {
|
||||
console.error('System health check failed:', error);
|
||||
setIsOnline(false);
|
||||
setLastCheck(new Date());
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// İlk kontrol
|
||||
checkSystemHealth();
|
||||
|
||||
// Her 30 saniyede bir kontrol et
|
||||
const interval = setInterval(checkSystemHealth, 30000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
isOnline,
|
||||
lastCheck,
|
||||
recheckHealth: checkSystemHealth,
|
||||
};
|
||||
};
|
||||
|
|
@ -355,7 +355,7 @@ const Wizard = () => {
|
|||
defaultTitle={APP_NAME}
|
||||
/>
|
||||
|
||||
<div className="mb-8">
|
||||
<div className="mb-6 mt-2">
|
||||
<Steps current={currentStep}>
|
||||
<Steps.Item title={translate('::ListForms.Wizard.MenuInfo') || 'Menu Info'} />
|
||||
<Steps.Item
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
toast,
|
||||
Tooltip,
|
||||
} from '@/components/ui'
|
||||
import SqlEditor from '@/views/sqlQueryManager/components/SqlEditor'
|
||||
import SqlEditor from '@/views/developerKit/SqlEditor'
|
||||
import { ListFormJsonRowDto } from '@/proxy/admin/list-form/models'
|
||||
import { SelectBoxOption } from '@/types/shared'
|
||||
import { useStoreActions, useStoreState } from '@/store'
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
import CodeLayout from '@/components/developerKit/CodeLayout'
|
||||
import React from 'react'
|
||||
|
||||
const CodePage: React.FC = () => {
|
||||
return <CodeLayout />
|
||||
}
|
||||
|
||||
export default CodePage
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import ComponentEditor from '@/components/developerKit/ComponentEditor'
|
||||
import ComponentEditor from '@/views/developerKit/ComponentEditor'
|
||||
import DeveloperLayout from '@/components/layouts/DeveloperLayout'
|
||||
import React from 'react'
|
||||
|
||||
const ComponentDetailPage: React.FC = () => {
|
||||
const ComponentEditorPage: React.FC = () => {
|
||||
return (
|
||||
<DeveloperLayout>
|
||||
<ComponentEditor />
|
||||
|
|
@ -10,4 +10,4 @@ const ComponentDetailPage: React.FC = () => {
|
|||
)
|
||||
}
|
||||
|
||||
export default ComponentDetailPage
|
||||
export default ComponentEditorPage
|
||||
|
|
@ -16,7 +16,9 @@ import {
|
|||
} from 'react-icons/fa'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { Loading } from '../shared'
|
||||
import { Loading } from '../../components/shared'
|
||||
import { APP_NAME } from '@/constants/app.constant'
|
||||
import { Helmet } from 'react-helmet'
|
||||
|
||||
const ComponentManager: React.FC = () => {
|
||||
const { components, loading, updateComponent, deleteComponent } = useComponents()
|
||||
|
|
@ -89,24 +91,13 @@ const ComponentManager: React.FC = () => {
|
|||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900">
|
||||
{translate('::App.DeveloperKit.Components')}
|
||||
</h1>
|
||||
<p className="text-slate-600">{translate('::App.DeveloperKit.Component.Description')}</p>
|
||||
</div>
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.componentsNew}
|
||||
className="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
{translate('::App.DeveloperKit.Component.New')}
|
||||
</Link>
|
||||
</div>
|
||||
<Helmet
|
||||
titleTemplate={`%s | ${APP_NAME}`}
|
||||
title={translate('::' + 'App.DeveloperKit.Components')}
|
||||
defaultTitle={APP_NAME}
|
||||
></Helmet>
|
||||
|
||||
{/* Statistics Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-2 mb-4">
|
||||
{stats.map((stat, index) => (
|
||||
<div key={index} className="bg-white rounded-lg border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -150,6 +141,15 @@ const ComponentManager: React.FC = () => {
|
|||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.componentsNew}
|
||||
className="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
{translate('::App.DeveloperKit.Component.New')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Components List */}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import ComponentManager from '@/components/developerKit/ComponentManager'
|
||||
import ComponentManager from '@/views/developerKit/ComponentManager'
|
||||
import DeveloperLayout from '@/components/layouts/DeveloperLayout'
|
||||
import React from 'react'
|
||||
|
||||
const ComponentPage: React.FC = () => {
|
||||
const ComponentManagerPage: React.FC = () => {
|
||||
return (
|
||||
<DeveloperLayout>
|
||||
<ComponentManager />
|
||||
|
|
@ -10,4 +10,4 @@ const ComponentPage: React.FC = () => {
|
|||
)
|
||||
}
|
||||
|
||||
export default ComponentPage
|
||||
export default ComponentManagerPage
|
||||
876
ui/src/views/developerKit/CrudEndpointManager.tsx
Normal file
876
ui/src/views/developerKit/CrudEndpointManager.tsx
Normal file
|
|
@ -0,0 +1,876 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import axios from 'axios'
|
||||
import {
|
||||
FaSearch,
|
||||
FaGlobe,
|
||||
FaCopy,
|
||||
FaCheckCircle,
|
||||
FaExclamationCircle,
|
||||
FaDatabase,
|
||||
FaSyncAlt,
|
||||
FaPaperPlane,
|
||||
FaTrash,
|
||||
FaBolt,
|
||||
FaTable,
|
||||
FaToggleOn,
|
||||
FaToggleOff,
|
||||
FaChevronRight,
|
||||
FaChevronDown,
|
||||
FaServer,
|
||||
} from 'react-icons/fa'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { getDataSources } from '@/services/data-source.service'
|
||||
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
||||
import { developerKitService } from '@/services/developerKit.service'
|
||||
import type { DataSourceDto } from '@/proxy/data-source'
|
||||
import type { DatabaseTableDto } from '@/proxy/sql-query-manager/models'
|
||||
import type { CrudEndpoint, SqlTable } from '@/proxy/developerKit/models'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { APP_NAME } from '@/constants/app.constant'
|
||||
|
||||
interface TestResult {
|
||||
success: boolean
|
||||
status: number
|
||||
data?: unknown
|
||||
error?: unknown
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
interface ParameterInput {
|
||||
name: string
|
||||
value: string
|
||||
type: 'path' | 'query' | 'body'
|
||||
required: boolean
|
||||
description?: string
|
||||
}
|
||||
|
||||
// Helper: tableName -> PascalCase entity name
|
||||
function toPascalCase(tableName: string): string {
|
||||
const name = tableName.replace(/^.*\./g, '')
|
||||
return name
|
||||
.replace(/[_-]([a-z])/gi, (_: string, c: string) => c.toUpperCase())
|
||||
.replace(/^([a-z])/, (c: string) => c.toUpperCase())
|
||||
}
|
||||
|
||||
const METHOD_COLOR: Record<string, string> = {
|
||||
GET: 'bg-blue-100 text-blue-800 border-blue-200',
|
||||
POST: 'bg-green-100 text-green-800 border-green-200',
|
||||
PUT: 'bg-yellow-100 text-yellow-800 border-yellow-200',
|
||||
DELETE: 'bg-red-100 text-red-800 border-red-200',
|
||||
}
|
||||
|
||||
const CrudEndpointManager: React.FC = () => {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
// Local entity + endpoint state (no context dependency)
|
||||
const [entities, setEntities] = useState<SqlTable[]>([])
|
||||
const [generatedEndpoints, setGeneratedEndpoints] = useState<CrudEndpoint[]>([])
|
||||
|
||||
const refreshData = useCallback(async () => {
|
||||
try {
|
||||
const [entitiesRes, endpointsRes] = await Promise.all([
|
||||
developerKitService.getSqlTables(),
|
||||
developerKitService.getGeneratedEndpoints(),
|
||||
])
|
||||
setEntities(entitiesRes.items || [])
|
||||
setGeneratedEndpoints(endpointsRes.items || [])
|
||||
} catch (err) {
|
||||
console.error('Failed to refresh data', err)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
refreshData()
|
||||
}, [])
|
||||
|
||||
// Data source + tables
|
||||
const [dataSources, setDataSources] = useState<DataSourceDto[]>([])
|
||||
const [selectedDataSource, setSelectedDataSource] = useState<string | null>(null)
|
||||
const [dbTables, setDbTables] = useState<DatabaseTableDto[]>([])
|
||||
const [loadingTables, setLoadingTables] = useState(false)
|
||||
|
||||
// Selection
|
||||
const [selectedTable, setSelectedTable] = useState<DatabaseTableDto | null>(null)
|
||||
const [tableSearch, setTableSearch] = useState('')
|
||||
const [crudFilter, setCrudFilter] = useState<'all' | 'with' | 'without'>('all')
|
||||
|
||||
// Endpoint management state
|
||||
const [generatingFor, setGeneratingFor] = useState<string | null>(null)
|
||||
const [deletingAll, setDeletingAll] = useState<string | null>(null)
|
||||
const [togglingId, setTogglingId] = useState<string | null>(null)
|
||||
const [expandedEndpoint, setExpandedEndpoint] = useState<string | null>(null)
|
||||
|
||||
// Test state
|
||||
const [testResults, setTestResults] = useState<Record<string, TestResult>>({})
|
||||
const [loadingEndpoints, setLoadingEndpoints] = useState<Set<string>>(new Set())
|
||||
const [parameterValues, setParameterValues] = useState<Record<string, Record<string, string>>>({})
|
||||
const [requestBodies, setRequestBodies] = useState<Record<string, string>>({})
|
||||
|
||||
// Load data sources on mount
|
||||
useEffect(() => {
|
||||
getDataSources()
|
||||
.then((res) => {
|
||||
const items = res.data.items || []
|
||||
setDataSources(items)
|
||||
if (items.length > 0) {
|
||||
setSelectedDataSource(items[0].code ?? null)
|
||||
}
|
||||
})
|
||||
.catch(console.error)
|
||||
}, [])
|
||||
|
||||
// Load tables when datasource changes
|
||||
useEffect(() => {
|
||||
if (!selectedDataSource) return
|
||||
setLoadingTables(true)
|
||||
setDbTables([])
|
||||
setSelectedTable(null)
|
||||
sqlObjectManagerService
|
||||
.getAllObjects(selectedDataSource)
|
||||
.then((res) => {
|
||||
setDbTables(res.data.tables || [])
|
||||
})
|
||||
.catch(console.error)
|
||||
.finally(() => setLoadingTables(false))
|
||||
}, [selectedDataSource])
|
||||
|
||||
// Helpers
|
||||
const getEntityForTable = useCallback(
|
||||
(tableName: string): SqlTable | undefined =>
|
||||
entities.find((e) => e.tableName.toLowerCase() === tableName.toLowerCase()),
|
||||
[entities],
|
||||
)
|
||||
|
||||
const getEndpointsForTable = useCallback(
|
||||
(tableName: string): CrudEndpoint[] => {
|
||||
const entity = getEntityForTable(tableName)
|
||||
if (!entity) return []
|
||||
return generatedEndpoints.filter((ep) => ep.entityId === entity.id)
|
||||
},
|
||||
[entities, generatedEndpoints, getEntityForTable],
|
||||
)
|
||||
|
||||
const activeEndpointCount = (tableName: string) =>
|
||||
getEndpointsForTable(tableName).filter((ep) => ep.isActive).length
|
||||
|
||||
const allEndpointCount = (tableName: string) => getEndpointsForTable(tableName).length
|
||||
|
||||
// Filtered table list
|
||||
const filteredTables = dbTables.filter((t) => {
|
||||
const matchesSearch =
|
||||
t.fullName.toLowerCase().includes(tableSearch.toLowerCase()) ||
|
||||
t.tableName.toLowerCase().includes(tableSearch.toLowerCase())
|
||||
const hasCrud = allEndpointCount(t.tableName) > 0
|
||||
const matchesCrudFilter =
|
||||
crudFilter === 'all' ||
|
||||
(crudFilter === 'with' && hasCrud) ||
|
||||
(crudFilter === 'without' && !hasCrud)
|
||||
return matchesSearch && matchesCrudFilter
|
||||
})
|
||||
|
||||
// Group by schema
|
||||
const tablesBySchema = filteredTables.reduce<Record<string, DatabaseTableDto[]>>((acc, t) => {
|
||||
const schema = t.schemaName || 'dbo'
|
||||
if (!acc[schema]) acc[schema] = []
|
||||
acc[schema].push(t)
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
// Generate CRUD endpoints for selected table
|
||||
const handleGenerate = async (table: DatabaseTableDto) => {
|
||||
const key = table.fullName
|
||||
setGeneratingFor(key)
|
||||
try {
|
||||
let entity = getEntityForTable(table.tableName)
|
||||
if (!entity) {
|
||||
const entityName = toPascalCase(table.tableName)
|
||||
await developerKitService.createSqlTable({
|
||||
menu: 'auto',
|
||||
name: entityName,
|
||||
displayName: entityName,
|
||||
tableName: table.tableName,
|
||||
description: `Auto-created from ${table.fullName}`,
|
||||
isActive: true,
|
||||
isFullAuditedEntity: false,
|
||||
isMultiTenant: false,
|
||||
fields: [],
|
||||
})
|
||||
const fresh = await developerKitService.getSqlTables()
|
||||
setEntities(fresh.items || [])
|
||||
entity = (fresh.items || []).find(
|
||||
(e) => e.tableName.toLowerCase() === table.tableName.toLowerCase(),
|
||||
)
|
||||
}
|
||||
if (!entity) throw new Error('Entity could not be created')
|
||||
const result = await developerKitService.generateCrudEndpoints(entity.id)
|
||||
setGeneratedEndpoints((prev) => [
|
||||
...prev.filter((ep) => ep.entityId !== entity!.id),
|
||||
...(result.items || []),
|
||||
])
|
||||
} catch (err) {
|
||||
console.error('Generate failed', err)
|
||||
} finally {
|
||||
setGeneratingFor(null)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all endpoints for a table
|
||||
const handleDeleteAll = async (table: DatabaseTableDto) => {
|
||||
const key = table.fullName
|
||||
setDeletingAll(key)
|
||||
try {
|
||||
const endpoints = getEndpointsForTable(table.tableName)
|
||||
await Promise.all(endpoints.map((ep) => developerKitService.deleteGeneratedEndpoint(ep.id)))
|
||||
const deletedIds = new Set(endpoints.map((ep) => ep.id))
|
||||
setGeneratedEndpoints((prev) => prev.filter((ep) => !deletedIds.has(ep.id)))
|
||||
} catch (err) {
|
||||
console.error('Delete failed', err)
|
||||
} finally {
|
||||
setDeletingAll(null)
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle single endpoint
|
||||
const handleToggle = async (endpointId: string) => {
|
||||
setTogglingId(endpointId)
|
||||
try {
|
||||
const updated = await developerKitService.toggleGeneratedEndpoint(endpointId)
|
||||
setGeneratedEndpoints((prev) => prev.map((ep) => (ep.id === endpointId ? updated : ep)))
|
||||
} catch (err) {
|
||||
console.error('Toggle failed', err)
|
||||
} finally {
|
||||
setTogglingId(null)
|
||||
}
|
||||
}
|
||||
|
||||
// Test endpoint helpers
|
||||
const getEndpointParameters = (endpoint: CrudEndpoint): ParameterInput[] => {
|
||||
const params: ParameterInput[] = []
|
||||
const vals = parameterValues[endpoint.id] || {}
|
||||
switch (endpoint.operationType) {
|
||||
case 'GetById':
|
||||
case 'Update':
|
||||
case 'Delete':
|
||||
params.push({
|
||||
name: 'id',
|
||||
value: vals.id || '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
||||
type: 'path',
|
||||
required: true,
|
||||
description: 'Entity ID',
|
||||
})
|
||||
break
|
||||
case 'GetList':
|
||||
params.push(
|
||||
{
|
||||
name: 'SkipCount',
|
||||
value: vals.SkipCount || '0',
|
||||
type: 'query',
|
||||
required: false,
|
||||
description: 'Skip count',
|
||||
},
|
||||
{
|
||||
name: 'MaxResultCount',
|
||||
value: vals.MaxResultCount || '10',
|
||||
type: 'query',
|
||||
required: false,
|
||||
description: 'Max records',
|
||||
},
|
||||
)
|
||||
break
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
const needsBody = (ep: CrudEndpoint) =>
|
||||
ep.operationType === 'Create' || ep.operationType === 'Update'
|
||||
|
||||
const getRequestBody = (ep: CrudEndpoint) => {
|
||||
if (requestBodies[ep.id]) return requestBodies[ep.id]
|
||||
return JSON.stringify(
|
||||
{ name: 'Sample Item', description: 'Description', isActive: true },
|
||||
null,
|
||||
2,
|
||||
)
|
||||
}
|
||||
|
||||
const testEndpoint = async (endpoint: CrudEndpoint) => {
|
||||
setLoadingEndpoints((prev) => new Set(prev).add(endpoint.id))
|
||||
try {
|
||||
let url = `${import.meta.env.VITE_API_URL}/api/app/crudendpoint/${endpoint.entityName?.toLowerCase()}`
|
||||
const params = getEndpointParameters(endpoint)
|
||||
const pathParam = params.find((p) => p.type === 'path')
|
||||
if (pathParam) url += `/${pathParam.value}`
|
||||
const queryParams = params.filter((p) => p.type === 'query')
|
||||
if (queryParams.length) {
|
||||
url += '?' + queryParams.map((p) => `${p.name}=${encodeURIComponent(p.value)}`).join('&')
|
||||
}
|
||||
let data = undefined
|
||||
if (needsBody(endpoint)) {
|
||||
try {
|
||||
data = JSON.parse(getRequestBody(endpoint))
|
||||
} catch {
|
||||
data = {}
|
||||
}
|
||||
}
|
||||
const res = await axios({
|
||||
method: endpoint.method,
|
||||
url,
|
||||
timeout: 10000,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data,
|
||||
})
|
||||
setTestResults((prev) => ({
|
||||
...prev,
|
||||
[endpoint.id]: {
|
||||
success: true,
|
||||
status: res.status,
|
||||
data: res.data,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
}))
|
||||
} catch (error: unknown) {
|
||||
const axiosErr = error as { response?: { status?: number; data?: unknown }; message?: string }
|
||||
setTestResults((prev) => ({
|
||||
...prev,
|
||||
[endpoint.id]: {
|
||||
success: false,
|
||||
status: axiosErr.response?.status || 0,
|
||||
error: axiosErr.response?.data || axiosErr.message,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
}))
|
||||
} finally {
|
||||
setLoadingEndpoints((prev) => {
|
||||
const s = new Set(prev)
|
||||
s.delete(endpoint.id)
|
||||
return s
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Derived stats
|
||||
const tablesWithEndpoints = dbTables.filter((t) => allEndpointCount(t.tableName) > 0).length
|
||||
const totalActiveEndpoints = generatedEndpoints.filter((ep) => ep.isActive).length
|
||||
const selectedTableEndpoints = selectedTable ? getEndpointsForTable(selectedTable.tableName) : []
|
||||
const selectedEntity = selectedTable ? getEntityForTable(selectedTable.tableName) : undefined
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full gap-4">
|
||||
<Helmet
|
||||
titleTemplate={`%s | ${APP_NAME}`}
|
||||
title={translate('::' + 'App.DeveloperKit.CrudEndpoints')}
|
||||
defaultTitle={APP_NAME}
|
||||
></Helmet>
|
||||
|
||||
{/* Stats Row */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mt-2">
|
||||
<div className="bg-white rounded-lg border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600 mb-1">Toplam Tablo</p>
|
||||
<p className="text-3xl font-bold text-slate-900">{dbTables.length}</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-blue-100">
|
||||
<FaDatabase className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600 mb-1">Endpoint Kurulu</p>
|
||||
<p className="text-3xl font-bold text-slate-900">{tablesWithEndpoints}</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-green-100">
|
||||
<FaCheckCircle className="w-6 h-6 text-green-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600 mb-1">Aktif Endpoint</p>
|
||||
<p className="text-3xl font-bold text-slate-900">{totalActiveEndpoints}</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-emerald-100">
|
||||
<FaBolt className="w-6 h-6 text-emerald-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg border border-slate-200 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-600 mb-1">Veri Kaynagi</p>
|
||||
<p className="text-3xl font-bold text-slate-900">{dataSources.length}</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-purple-100">
|
||||
<FaServer className="w-6 h-6 text-purple-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main two-panel layout */}
|
||||
<div className="flex gap-4" style={{ height: 'calc(100vh - 250px)', minHeight: 400 }}>
|
||||
{/* Left Panel: Table List */}
|
||||
<div className="w-72 flex-shrink-0 flex flex-col bg-white rounded-lg border border-slate-200 overflow-hidden">
|
||||
{/* DataSource selector */}
|
||||
<div className="p-3 border-b border-slate-200 bg-slate-50">
|
||||
<select
|
||||
value={selectedDataSource || ''}
|
||||
onChange={(e) => setSelectedDataSource(e.target.value)}
|
||||
className="w-full px-2 py-1.5 text-sm border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white"
|
||||
>
|
||||
{dataSources.map((ds) => (
|
||||
<option key={ds.id} value={ds.code ?? ''}>
|
||||
{ds.code}
|
||||
</option>
|
||||
))}
|
||||
{dataSources.length === 0 && <option value="">Yukleniyor...</option>}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Search + CRUD filter */}
|
||||
<div className="p-3 border-b border-slate-200 space-y-2">
|
||||
<div className="relative">
|
||||
<FaSearch className="absolute left-2.5 top-1/2 -translate-y-1/2 text-slate-400 text-xs" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Tablo ara..."
|
||||
value={tableSearch}
|
||||
onChange={(e) => setTableSearch(e.target.value)}
|
||||
className="w-full pl-7 pr-3 py-1.5 text-sm border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex rounded-lg border border-slate-200 overflow-hidden text-xs font-medium">
|
||||
{(['all', 'with', 'without'] as const).map((f) => {
|
||||
const labels = {
|
||||
all: `Tümü (${dbTables.length})`,
|
||||
with: `VAR (${dbTables.filter((t) => allEndpointCount(t.tableName) > 0).length})`,
|
||||
without: `YOK (${dbTables.filter((t) => allEndpointCount(t.tableName) === 0).length})`,
|
||||
}
|
||||
const active = crudFilter === f
|
||||
return (
|
||||
<button
|
||||
key={f}
|
||||
onClick={() => setCrudFilter(f)}
|
||||
className={`flex-1 py-1.5 transition-colors ${
|
||||
active
|
||||
? f === 'with'
|
||||
? 'bg-green-500 text-white'
|
||||
: f === 'without'
|
||||
? 'bg-slate-500 text-white'
|
||||
: 'bg-blue-500 text-white'
|
||||
: 'bg-white text-slate-500 hover:bg-slate-50'
|
||||
}`}
|
||||
>
|
||||
{labels[f]}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table list */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{loadingTables ? (
|
||||
<div className="flex items-center justify-center p-8 text-slate-400">
|
||||
<FaSyncAlt className="animate-spin mr-2" />
|
||||
<span className="text-sm">Yukleniyor...</span>
|
||||
</div>
|
||||
) : filteredTables.length === 0 ? (
|
||||
<div className="p-6 text-center text-slate-400 text-sm">
|
||||
{selectedDataSource ? 'Tablo bulunamadi' : 'Veri kaynagi secin'}
|
||||
</div>
|
||||
) : (
|
||||
Object.entries(tablesBySchema).map(([schema, tables]) => (
|
||||
<div key={schema}>
|
||||
<div className="px-3 py-1.5 text-xs font-semibold text-slate-400 uppercase tracking-wide bg-slate-50 border-b border-slate-100 sticky top-0">
|
||||
{schema}
|
||||
</div>
|
||||
{tables.map((table) => {
|
||||
const active = activeEndpointCount(table.tableName)
|
||||
const total = allEndpointCount(table.tableName)
|
||||
const isSelected = selectedTable?.fullName === table.fullName
|
||||
const hasEndpoints = total > 0
|
||||
return (
|
||||
<button
|
||||
key={table.fullName}
|
||||
onClick={() => setSelectedTable(table)}
|
||||
className={`w-full flex items-center justify-between px-3 py-1 text-left hover:bg-slate-50 transition-colors border-b border-slate-50 ${
|
||||
isSelected ? 'bg-blue-50 border-l-2 border-l-blue-500' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<FaTable
|
||||
className={`flex-shrink-0 text-xs ${
|
||||
hasEndpoints ? 'text-green-500' : 'text-slate-300'
|
||||
}`}
|
||||
/>
|
||||
<span className="text-sm text-slate-700 truncate">{table.tableName}</span>
|
||||
</div>
|
||||
{hasEndpoints && (
|
||||
<span
|
||||
className={`flex-shrink-0 text-xs px-1.5 py-0.5 rounded-full font-medium ${
|
||||
active > 0
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-slate-100 text-slate-500'
|
||||
}`}
|
||||
>
|
||||
{active}/{total}
|
||||
</span>
|
||||
)}
|
||||
{isSelected && (
|
||||
<FaChevronRight className="flex-shrink-0 text-blue-400 text-xs ml-1" />
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Panel: Endpoint Management */}
|
||||
<div className="flex-1 min-w-0 flex flex-col bg-white rounded-lg border border-slate-200 overflow-hidden">
|
||||
{!selectedTable ? (
|
||||
<div className="flex-1 flex flex-col items-center justify-center text-slate-400 p-8">
|
||||
<FaDatabase className="text-4xl mb-3 text-slate-200" />
|
||||
<p className="text-base font-medium">Soldan bir tablo secin</p>
|
||||
<p className="text-sm mt-1">
|
||||
Secilen tablo icin CRUD endpointlerini yonetebilirsiniz
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
{/* Table header */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-slate-200 bg-slate-50">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="bg-blue-100 text-blue-600 p-2 rounded-lg">
|
||||
<FaTable />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-slate-900">{selectedTable.schemaName}.{selectedTable.tableName}</h4>
|
||||
</div>
|
||||
{selectedEntity && (
|
||||
<span className="ml-2 text-xs bg-blue-100 text-blue-700 px-2 py-1 rounded-full">
|
||||
Entity: {selectedEntity.name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedTableEndpoints.length > 0 && (
|
||||
<button
|
||||
onClick={() => handleDeleteAll(selectedTable)}
|
||||
disabled={deletingAll === selectedTable.fullName}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm text-red-600 border border-red-200 rounded-lg hover:bg-red-50 disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{deletingAll === selectedTable.fullName ? (
|
||||
<FaSyncAlt className="animate-spin" />
|
||||
) : (
|
||||
<FaTrash />
|
||||
)}
|
||||
Tumunu Sil
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleGenerate(selectedTable)}
|
||||
disabled={generatingFor === selectedTable.fullName}
|
||||
className="flex items-center gap-2 px-4 py-1.5 text-sm font-medium bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{generatingFor === selectedTable.fullName ? (
|
||||
<FaSyncAlt className="animate-spin" />
|
||||
) : (
|
||||
<FaBolt />
|
||||
)}
|
||||
{selectedTableEndpoints.length > 0
|
||||
? 'Yeniden Olustur'
|
||||
: 'CRUD Endpoint Olustur'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Endpoints list */}
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
{selectedTableEndpoints.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-16 text-slate-400">
|
||||
<FaBolt className="text-3xl mb-3 text-slate-200" />
|
||||
<p className="font-medium">Henuz endpoint yok</p>
|
||||
<p className="text-sm mt-1">
|
||||
{selectedEntity
|
||||
? '"CRUD Endpoint Olustur" butonuna tiklayin'
|
||||
: 'Bu tablo icin Sql Table otomatik olusturulacak ve 5 endpoint uretilecektir'}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{selectedTableEndpoints.map((ep) => {
|
||||
const isExpanded = expandedEndpoint === ep.id
|
||||
const testResult = testResults[ep.id]
|
||||
return (
|
||||
<div
|
||||
key={ep.id}
|
||||
className="border border-slate-200 rounded-lg overflow-hidden"
|
||||
>
|
||||
{/* Endpoint row */}
|
||||
<div className="flex items-center gap-3 p-3 bg-white hover:bg-slate-50 transition-colors">
|
||||
{/* Toggle */}
|
||||
<button
|
||||
onClick={() => handleToggle(ep.id)}
|
||||
disabled={togglingId === ep.id}
|
||||
title={ep.isActive ? 'Devre disi birak' : 'Etkinlestir'}
|
||||
className={`flex-shrink-0 text-xl transition-colors ${
|
||||
ep.isActive
|
||||
? 'text-green-500 hover:text-green-700'
|
||||
: 'text-slate-300 hover:text-slate-500'
|
||||
} disabled:opacity-40`}
|
||||
>
|
||||
{togglingId === ep.id ? (
|
||||
<FaSyncAlt className="animate-spin text-base" />
|
||||
) : ep.isActive ? (
|
||||
<FaToggleOn />
|
||||
) : (
|
||||
<FaToggleOff />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Method badge */}
|
||||
<span
|
||||
className={`flex-shrink-0 px-2 py-0.5 text-xs font-bold rounded border ${
|
||||
METHOD_COLOR[ep.method] ||
|
||||
'bg-slate-100 text-slate-700 border-slate-200'
|
||||
}`}
|
||||
>
|
||||
{ep.method}
|
||||
</span>
|
||||
|
||||
{/* Operation */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="font-medium text-slate-800 text-sm">
|
||||
{ep.operationType}
|
||||
</span>
|
||||
<code className="ml-3 text-xs text-slate-500 bg-slate-100 px-2 py-0.5 rounded">
|
||||
{ep.path}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
{/* Status */}
|
||||
{ep.isActive ? (
|
||||
<span className="text-xs text-green-600 font-medium">Aktif</span>
|
||||
) : (
|
||||
<span className="text-xs text-slate-400">Devre disi</span>
|
||||
)}
|
||||
|
||||
{/* Copy path */}
|
||||
<button
|
||||
onClick={() => navigator.clipboard.writeText(ep.path)}
|
||||
className="p-1.5 text-slate-400 hover:text-slate-700 transition-colors"
|
||||
title="Path kopyala"
|
||||
>
|
||||
<FaCopy className="text-xs" />
|
||||
</button>
|
||||
|
||||
{/* Expand */}
|
||||
<button
|
||||
onClick={() => setExpandedEndpoint(isExpanded ? null : ep.id)}
|
||||
className="p-1.5 text-slate-400 hover:text-slate-700 transition-colors"
|
||||
title="Test et / detaylar"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<FaChevronDown className="text-xs" />
|
||||
) : (
|
||||
<FaChevronRight className="text-xs" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Expanded detail + test */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-slate-200 bg-slate-50 p-4 space-y-4">
|
||||
{/* Parameters */}
|
||||
{getEndpointParameters(ep).length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-slate-600 mb-2">
|
||||
Parametreler
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
{getEndpointParameters(ep).map((param) => (
|
||||
<div key={param.name} className="flex items-center gap-2">
|
||||
<span
|
||||
className={`text-xs px-1.5 py-0.5 rounded font-mono ${
|
||||
param.type === 'path'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'bg-green-100 text-green-700'
|
||||
}`}
|
||||
>
|
||||
{param.name}
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
value={
|
||||
parameterValues[ep.id]?.[param.name] ?? param.value
|
||||
}
|
||||
onChange={(e) =>
|
||||
setParameterValues((prev) => ({
|
||||
...prev,
|
||||
[ep.id]: {
|
||||
...prev[ep.id],
|
||||
[param.name]: e.target.value,
|
||||
},
|
||||
}))
|
||||
}
|
||||
className="flex-1 px-2 py-1 text-xs border border-slate-300 rounded focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Request body */}
|
||||
{needsBody(ep) && (
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-slate-600 mb-2">
|
||||
Request Body (JSON)
|
||||
</p>
|
||||
<textarea
|
||||
value={getRequestBody(ep)}
|
||||
onChange={(e) =>
|
||||
setRequestBodies((prev) => ({
|
||||
...prev,
|
||||
[ep.id]: e.target.value,
|
||||
}))
|
||||
}
|
||||
rows={5}
|
||||
className="w-full px-2 py-1.5 text-xs border border-slate-300 rounded font-mono focus:ring-1 focus:ring-blue-500 bg-white"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Test button */}
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => testEndpoint(ep)}
|
||||
disabled={loadingEndpoints.has(ep.id)}
|
||||
className="flex items-center gap-2 bg-blue-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-blue-700 disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{loadingEndpoints.has(ep.id) ? (
|
||||
<FaSyncAlt className="animate-spin" />
|
||||
) : (
|
||||
<FaPaperPlane />
|
||||
)}
|
||||
{loadingEndpoints.has(ep.id) ? 'Gonderiliyor...' : 'Test Et'}
|
||||
</button>
|
||||
{testResult && (
|
||||
<button
|
||||
onClick={() =>
|
||||
setTestResults((prev) => {
|
||||
const n = { ...prev }
|
||||
delete n[ep.id]
|
||||
return n
|
||||
})
|
||||
}
|
||||
className="text-xs text-slate-500 hover:text-slate-700 px-2 py-1.5"
|
||||
>
|
||||
Temizle
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Test result */}
|
||||
{testResult && (
|
||||
<div
|
||||
className={`rounded-lg border p-3 ${
|
||||
testResult.success
|
||||
? 'border-green-200 bg-green-50'
|
||||
: 'border-red-200 bg-red-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
{testResult.success ? (
|
||||
<FaCheckCircle className="text-green-500" />
|
||||
) : (
|
||||
<FaExclamationCircle className="text-red-500" />
|
||||
)}
|
||||
<span className="text-sm font-medium">
|
||||
HTTP {testResult.status}
|
||||
</span>
|
||||
<span className="text-xs text-slate-500 ml-auto">
|
||||
{new Date(testResult.timestamp).toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<pre className="text-xs bg-white border border-slate-200 rounded p-2 overflow-x-auto max-h-48">
|
||||
{JSON.stringify(
|
||||
testResult.success ? testResult.data : testResult.error,
|
||||
null,
|
||||
2,
|
||||
)}
|
||||
</pre>
|
||||
<button
|
||||
onClick={() =>
|
||||
navigator.clipboard.writeText(
|
||||
JSON.stringify(
|
||||
testResult.success ? testResult.data : testResult.error,
|
||||
null,
|
||||
2,
|
||||
),
|
||||
)
|
||||
}
|
||||
className="absolute top-1.5 right-1.5 p-1 text-slate-400 hover:text-slate-700"
|
||||
>
|
||||
<FaCopy className="text-xs" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* C# Code Preview */}
|
||||
{ep.csharpCode && (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<p className="text-xs font-semibold text-slate-600">C# Kodu</p>
|
||||
<button
|
||||
onClick={() => navigator.clipboard.writeText(ep.csharpCode)}
|
||||
className="text-xs text-slate-400 hover:text-slate-700 flex items-center gap-1"
|
||||
>
|
||||
<FaCopy /> Kopyala
|
||||
</button>
|
||||
</div>
|
||||
<pre className="text-xs bg-slate-800 text-green-300 rounded-lg p-3 overflow-x-auto max-h-48 font-mono">
|
||||
{ep.csharpCode}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer info */}
|
||||
{selectedTableEndpoints.length > 0 && (
|
||||
<div className="border-t border-slate-200 px-2 py-1 bg-slate-50 flex items-center gap-4 text-xs text-slate-500">
|
||||
<span className="flex items-center gap-1">
|
||||
<FaCheckCircle className="text-green-400" />
|
||||
{selectedTableEndpoints.filter((e) => e.isActive).length} aktif
|
||||
</span>
|
||||
<span>{selectedTableEndpoints.filter((e) => !e.isActive).length} devre disi</span>
|
||||
<span className="ml-auto">
|
||||
5 endpoint: GetList, GetById, Create, Update, Delete
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CrudEndpointManager
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import React from 'react'
|
||||
import { EntityProvider } from '@/contexts/EntityContext'
|
||||
import DeveloperLayout from '@/components/layouts/DeveloperLayout'
|
||||
import CrudEndpointManager from '@/components/developerKit/CrudEndpointManager'
|
||||
|
||||
const CrudEndpointPage: React.FC = () => {
|
||||
return (
|
||||
<DeveloperLayout>
|
||||
<EntityProvider>
|
||||
<CrudEndpointManager />
|
||||
</EntityProvider>
|
||||
</DeveloperLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default CrudEndpointPage
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import React from 'react'
|
||||
import { EntityProvider } from '@/contexts/EntityContext'
|
||||
import DeveloperLayout from '@/components/layouts/DeveloperLayout'
|
||||
import Dashboard from '@/components/developerKit/Dashboard'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { APP_NAME } from '@/constants/app.constant'
|
||||
|
||||
const DashboardPage: React.FC = () => {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet
|
||||
titleTemplate={`%s | ${APP_NAME}`}
|
||||
title={translate('::' + 'App.DeveloperKit')}
|
||||
defaultTitle={APP_NAME}
|
||||
></Helmet>
|
||||
<DeveloperLayout>
|
||||
<EntityProvider>
|
||||
<Dashboard />
|
||||
</EntityProvider>
|
||||
</DeveloperLayout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DashboardPage
|
||||
|
|
@ -21,6 +21,8 @@ import {
|
|||
postTestCompile,
|
||||
TestCompileDto,
|
||||
} from '@/services/dynamicService.service'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { APP_NAME } from '@/constants/app.constant'
|
||||
|
||||
const DynamicAppServiceEditor: React.FC = () => {
|
||||
// State
|
||||
|
|
@ -254,6 +256,12 @@ namespace DynamicServices
|
|||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Helmet
|
||||
titleTemplate={`%s | ${APP_NAME}`}
|
||||
title={translate('::' + 'App.DeveloperKit.DynamicServices')}
|
||||
defaultTitle={APP_NAME}
|
||||
></Helmet>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import React from 'react'
|
||||
import DynamicAppServiceEditor from '@/components/developerKit/DynamicAppServiceEditor'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import DeveloperLayout from '@/components/layouts/DeveloperLayout'
|
||||
|
||||
const DynamicServicePage: React.FC = () => {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
return (
|
||||
<DeveloperLayout>
|
||||
<DynamicAppServiceEditor />
|
||||
</DeveloperLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default DynamicServicePage
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import React from 'react'
|
||||
import { EntityProvider } from '@/contexts/EntityContext'
|
||||
import DeveloperLayout from '@/components/layouts/DeveloperLayout'
|
||||
import EntityEditor from '@/components/developerKit/EntityEditor'
|
||||
|
||||
const EntityDetailPage: React.FC = () => {
|
||||
return (
|
||||
<DeveloperLayout>
|
||||
<EntityProvider>
|
||||
<EntityEditor />
|
||||
</EntityProvider>
|
||||
</DeveloperLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default EntityDetailPage
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import React from 'react'
|
||||
import { EntityProvider } from '@/contexts/EntityContext'
|
||||
import DeveloperLayout from '@/components/layouts/DeveloperLayout'
|
||||
import EntityManager from '@/components/developerKit/EntityManager'
|
||||
|
||||
const EntityPage: React.FC = () => {
|
||||
return (
|
||||
<DeveloperLayout>
|
||||
<EntityProvider>
|
||||
<EntityManager />
|
||||
</EntityProvider>
|
||||
</DeveloperLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default EntityPage
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import React from 'react'
|
||||
import { EntityProvider } from '@/contexts/EntityContext'
|
||||
import DeveloperLayout from '@/components/layouts/DeveloperLayout'
|
||||
import MigrationManager from '@/components/developerKit/MigrationManager'
|
||||
|
||||
const MigrationPage: React.FC = () => {
|
||||
return (
|
||||
<DeveloperLayout>
|
||||
<EntityProvider>
|
||||
<MigrationManager />
|
||||
</EntityProvider>
|
||||
</DeveloperLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default MigrationPage
|
||||
|
|
@ -13,15 +13,14 @@ import type {
|
|||
} from '@/proxy/sql-query-manager/models'
|
||||
import { SqlObjectType } from '@/proxy/sql-query-manager/models'
|
||||
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
||||
import { FaDatabase, FaPlay, FaSave, FaCloudUploadAlt, FaCode, FaTable } from 'react-icons/fa'
|
||||
import { FaDatabase, FaPlay, FaSave, FaCloudUploadAlt, FaCode } from 'react-icons/fa'
|
||||
import { FaCheckCircle } from 'react-icons/fa'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import SqlObjectExplorer from './components/SqlObjectExplorer'
|
||||
import SqlEditor, { SqlEditorRef } from './components/SqlEditor'
|
||||
import SqlResultsGrid from './components/SqlResultsGrid'
|
||||
import SqlObjectProperties from './components/SqlObjectProperties'
|
||||
import TemplateDialog from './components/TemplateDialog'
|
||||
import TableDesignerDialog from './components/TableDesignerDialog'
|
||||
import SqlObjectExplorer from './SqlObjectExplorer'
|
||||
import SqlEditor, { SqlEditorRef } from './SqlEditor'
|
||||
import SqlResultsGrid from './SqlResultsGrid'
|
||||
import SqlObjectProperties from './SqlObjectProperties'
|
||||
import SqlTableDesignerDialog from './SqlTableDesignerDialog'
|
||||
import { Splitter } from '@/components/codeLayout/Splitter'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useStoreState } from '@/store/store'
|
||||
|
|
@ -72,18 +71,12 @@ const SqlQueryManager = () => {
|
|||
detectedName: '',
|
||||
isExistingObject: false,
|
||||
})
|
||||
const [showTemplateDialog, setShowTemplateDialog] = useState(false)
|
||||
const [showTableDesignerDialog, setShowTableDesignerDialog] = useState(false)
|
||||
const [designTableData, setDesignTableData] = useState<{ schemaName: string; tableName: string } | null>(null)
|
||||
const [showTemplateConfirmDialog, setShowTemplateConfirmDialog] = useState(false)
|
||||
const [pendingTemplate, setPendingTemplate] = useState<{ content: string; type: string } | null>(
|
||||
null,
|
||||
)
|
||||
|
||||
const handleDesignTable = (schemaName: string, tableName: string) => {
|
||||
setDesignTableData({ schemaName, tableName })
|
||||
setShowTableDesignerDialog(true)
|
||||
}
|
||||
const [showTableDesignerDialog, setShowTableDesignerDialog] = useState(false)
|
||||
const [designTableData, setDesignTableData] = useState<{ schemaName: string; tableName: string } | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
loadDataSources()
|
||||
|
|
@ -121,6 +114,16 @@ const SqlQueryManager = () => {
|
|||
}))
|
||||
}, [])
|
||||
|
||||
const handleNewTable = useCallback(() => {
|
||||
setDesignTableData(null)
|
||||
setShowTableDesignerDialog(true)
|
||||
}, [])
|
||||
|
||||
const handleDesignTable = useCallback((schemaName: string, tableName: string) => {
|
||||
setDesignTableData({ schemaName, tableName })
|
||||
setShowTableDesignerDialog(true)
|
||||
}, [])
|
||||
|
||||
const handleObjectSelect = useCallback(
|
||||
async (object: SqlObject | null, objectType: SqlObjectType | null) => {
|
||||
if (state.isDirty) {
|
||||
|
|
@ -878,12 +881,9 @@ GO`,
|
|||
selectedObject={state.selectedObject}
|
||||
onTemplateSelect={handleTemplateSelect}
|
||||
onShowTableColumns={handleShowTableColumns}
|
||||
onDesignTable={handleDesignTable}
|
||||
onNewTable={() => {
|
||||
setDesignTableData(null)
|
||||
setShowTableDesignerDialog(true)
|
||||
}}
|
||||
refreshTrigger={state.refreshTrigger}
|
||||
onNewTable={handleNewTable}
|
||||
onDesignTable={handleDesignTable}
|
||||
/>
|
||||
</div>
|
||||
</AdaptableCard>
|
||||
|
|
@ -975,27 +975,6 @@ GO`,
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table Designer Dialog */}
|
||||
<TableDesignerDialog
|
||||
isOpen={showTableDesignerDialog}
|
||||
onClose={() => {
|
||||
setShowTableDesignerDialog(false)
|
||||
setDesignTableData(null)
|
||||
}}
|
||||
dataSource={state.selectedDataSource}
|
||||
initialTableData={designTableData}
|
||||
onDeployed={() => {
|
||||
setState((prev) => ({ ...prev, refreshTrigger: prev.refreshTrigger + 1 }))
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Template Dialog */}
|
||||
<TemplateDialog
|
||||
isOpen={showTemplateDialog}
|
||||
onClose={() => setShowTemplateDialog(false)}
|
||||
onUseTemplate={handleUseTemplateFromDialog}
|
||||
/>
|
||||
|
||||
{/* Template Confirmation Dialog */}
|
||||
<Dialog
|
||||
isOpen={showTemplateConfirmDialog}
|
||||
|
|
@ -1016,6 +995,20 @@ GO`,
|
|||
</div>
|
||||
</Dialog>
|
||||
|
||||
{/* Table Designer Dialog */}
|
||||
<SqlTableDesignerDialog
|
||||
isOpen={showTableDesignerDialog}
|
||||
onClose={() => {
|
||||
setShowTableDesignerDialog(false)
|
||||
setDesignTableData(null)
|
||||
}}
|
||||
dataSource={state.selectedDataSource ?? ''}
|
||||
initialTableData={designTableData}
|
||||
onDeployed={() => {
|
||||
setState((prev) => ({ ...prev, refreshTrigger: prev.refreshTrigger + 1 }))
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Save Dialog */}
|
||||
<Dialog
|
||||
isOpen={showSaveDialog}
|
||||
|
|
@ -9,19 +9,15 @@ import {
|
|||
FaCloudUploadAlt,
|
||||
FaCheck,
|
||||
FaChevronRight,
|
||||
FaCog,
|
||||
FaDatabase,
|
||||
FaLink,
|
||||
FaEdit,
|
||||
FaTimes,
|
||||
FaArrowRight,
|
||||
FaInfoCircle,
|
||||
FaExclamationTriangle,
|
||||
} from 'react-icons/fa'
|
||||
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
||||
import { MenuService } from '@/services/menu.service'
|
||||
import { developerKitService } from '@/services/developerKit.service'
|
||||
import type { CreateUpdateCustomEntityDto, CreateUpdateCustomEntityFieldDto } from '@/proxy/developerKit/models'
|
||||
import type { CreateUpdateSqlTableDto, CreateUpdateSqlTableFieldDto } from '@/proxy/developerKit/models'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -482,10 +478,10 @@ function generateAlterTableSql(
|
|||
return lines.join('\n')
|
||||
}
|
||||
|
||||
// Map TableDesigner data-type → CustomEntityField type
|
||||
// Map TableDesigner data-type → SqlTableField type
|
||||
function colTypeToEntityFieldType(
|
||||
dt: SqlDataType,
|
||||
): CreateUpdateCustomEntityFieldDto['type'] {
|
||||
): CreateUpdateSqlTableFieldDto['type'] {
|
||||
if (dt === 'int' || dt === 'bigint') return 'number'
|
||||
if (dt === 'bit') return 'boolean'
|
||||
if (dt === 'datetime2' || dt === 'date') return 'date'
|
||||
|
|
@ -521,7 +517,7 @@ const DEFAULT_SETTINGS: TableSettings = {
|
|||
|
||||
// ─── Component ────────────────────────────────────────────────────────────────
|
||||
|
||||
const TableDesignerDialog = ({
|
||||
const SqlTableDesignerDialog = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
dataSource,
|
||||
|
|
@ -587,9 +583,9 @@ const TableDesignerDialog = ({
|
|||
.catch(() => {})
|
||||
.finally(() => setColsLoading(false))
|
||||
|
||||
// Load CustomEntity metadata (menu, entityName, displayName, flags…)
|
||||
// Load SqlTable metadata (menu, entityName, displayName, flags…)
|
||||
developerKitService
|
||||
.getCustomEntities()
|
||||
.getSqlTables()
|
||||
.then((res) => {
|
||||
const match = (res.items ?? []).find(
|
||||
(e) => e.tableName.toLowerCase() === initialTableData.tableName.toLowerCase(),
|
||||
|
|
@ -808,19 +804,19 @@ const TableDesignerDialog = ({
|
|||
|
||||
// ── Deploy ─────────────────────────────────────────────────────────────────
|
||||
|
||||
const syncCustomEntityMetadata = async (deployedTableName: string) => {
|
||||
const syncSqlTableMetadata = async (deployedTableName: string) => {
|
||||
try {
|
||||
const namedCols = columns.filter((c) => c.columnName.trim())
|
||||
|
||||
// Find existing CustomEntity record by table name
|
||||
const listResult = await developerKitService.getCustomEntities()
|
||||
// Find existing SqlTable record by table name
|
||||
const listResult = await developerKitService.getSqlTables()
|
||||
const existing = (listResult.items ?? []).find(
|
||||
(e) => e.tableName.toLowerCase() === deployedTableName.toLowerCase(),
|
||||
)
|
||||
|
||||
if (existing) {
|
||||
// Update: keep existing metadata, sync fields (match by name to preserve IDs)
|
||||
const fieldDtos: CreateUpdateCustomEntityFieldDto[] = namedCols.map((col, i) => {
|
||||
const fieldDtos: CreateUpdateSqlTableFieldDto[] = namedCols.map((col, i) => {
|
||||
const existingField = existing.fields?.find(
|
||||
(f) => f.name.toLowerCase() === col.columnName.trim().toLowerCase(),
|
||||
)
|
||||
|
|
@ -837,7 +833,7 @@ const TableDesignerDialog = ({
|
|||
displayOrder: i,
|
||||
}
|
||||
})
|
||||
const updateDto: CreateUpdateCustomEntityDto = {
|
||||
const updateDto: CreateUpdateSqlTableDto = {
|
||||
menu: existing.menu,
|
||||
name: existing.name,
|
||||
displayName: existing.displayName,
|
||||
|
|
@ -848,10 +844,10 @@ const TableDesignerDialog = ({
|
|||
isMultiTenant: existing.isMultiTenant,
|
||||
fields: fieldDtos,
|
||||
}
|
||||
await developerKitService.updateCustomEntity(existing.id, updateDto)
|
||||
await developerKitService.updateSqlTable(existing.id, updateDto)
|
||||
} else {
|
||||
// Create new CustomEntity record
|
||||
const fieldDtos: CreateUpdateCustomEntityFieldDto[] = namedCols.map((col, i) => {
|
||||
// Create new SqlTable record
|
||||
const fieldDtos: CreateUpdateSqlTableFieldDto[] = namedCols.map((col, i) => {
|
||||
const maxLen = col.maxLength ? parseInt(col.maxLength, 10) || undefined : undefined
|
||||
return {
|
||||
name: col.columnName.trim(),
|
||||
|
|
@ -864,7 +860,7 @@ const TableDesignerDialog = ({
|
|||
displayOrder: i,
|
||||
}
|
||||
})
|
||||
const createDto: CreateUpdateCustomEntityDto = {
|
||||
const createDto: CreateUpdateSqlTableDto = {
|
||||
menu: settings.menuValue,
|
||||
name: settings.entityName || deployedTableName,
|
||||
displayName: settings.displayName || deployedTableName,
|
||||
|
|
@ -875,7 +871,7 @@ const TableDesignerDialog = ({
|
|||
isMultiTenant: settings.isMultiTenant,
|
||||
fields: fieldDtos,
|
||||
}
|
||||
await developerKitService.createCustomEntity(createDto)
|
||||
await developerKitService.createSqlTable(createDto)
|
||||
}
|
||||
} catch {
|
||||
// Silent — SQL deploy already succeeded; metadata sync failure is non-critical
|
||||
|
|
@ -900,8 +896,8 @@ const TableDesignerDialog = ({
|
|||
})
|
||||
if (result.data.success) {
|
||||
const deployedTable = settings.tableName || initialTableData?.tableName || ''
|
||||
// Sync entity metadata to CustomEntity / CustomEntityField tables
|
||||
await syncCustomEntityMetadata(deployedTable)
|
||||
// Sync entity metadata to SqlTable / SqlTableField tables
|
||||
await syncSqlTableMetadata(deployedTable)
|
||||
toast.push(
|
||||
<Notification type="success" title="Basarili">
|
||||
Tablo basariyla {isEditMode ? 'güncellendi' : 'oluşturuldu'}: [dbo].[{deployedTable}]
|
||||
|
|
@ -1700,4 +1696,4 @@ const TableDesignerDialog = ({
|
|||
)
|
||||
}
|
||||
|
||||
export default TableDesignerDialog
|
||||
export default SqlTableDesignerDialog
|
||||
|
|
@ -1,292 +0,0 @@
|
|||
import { useState } from 'react'
|
||||
import { Dialog, Button } from '@/components/ui'
|
||||
import { FaCode } from 'react-icons/fa'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
interface TemplateDialogProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onUseTemplate: (templateContent: string, templateType: string) => void
|
||||
}
|
||||
|
||||
interface Template {
|
||||
id: string
|
||||
label: string
|
||||
description: string
|
||||
type: string
|
||||
}
|
||||
|
||||
const templates: Template[] = [
|
||||
{
|
||||
id: 'select',
|
||||
label: 'SELECT Query',
|
||||
description: 'Basic SELECT query template',
|
||||
type: 'select',
|
||||
},
|
||||
{
|
||||
id: 'insert',
|
||||
label: 'INSERT Query',
|
||||
description: 'Basic INSERT query template',
|
||||
type: 'insert',
|
||||
},
|
||||
{
|
||||
id: 'update',
|
||||
label: 'UPDATE Query',
|
||||
description: 'Basic UPDATE query template',
|
||||
type: 'update',
|
||||
},
|
||||
{
|
||||
id: 'delete',
|
||||
label: 'DELETE Query',
|
||||
description: 'Basic DELETE query template',
|
||||
type: 'delete',
|
||||
},
|
||||
{
|
||||
id: 'create-procedure',
|
||||
label: 'Stored Procedure',
|
||||
description: 'Create Stored Procedure template',
|
||||
type: 'create-procedure',
|
||||
},
|
||||
{
|
||||
id: 'create-view',
|
||||
label: 'View',
|
||||
description: 'Create View template',
|
||||
type: 'create-view',
|
||||
},
|
||||
{
|
||||
id: 'create-scalar-function',
|
||||
label: 'Scalar Function',
|
||||
description: 'Create Scalar Function template',
|
||||
type: 'create-scalar-function',
|
||||
},
|
||||
{
|
||||
id: 'create-table-function',
|
||||
label: 'Table-Valued Function',
|
||||
description: 'Create Table-Valued Function template',
|
||||
type: 'create-table-function',
|
||||
},
|
||||
]
|
||||
|
||||
const getTemplateContent = (templateType: string): string => {
|
||||
const templateContents: Record<string, string> = {
|
||||
select: `-- Basic SELECT query
|
||||
SELECT
|
||||
Column1,
|
||||
Column2,
|
||||
Column3
|
||||
FROM
|
||||
TableName
|
||||
WHERE
|
||||
-- Add your conditions
|
||||
Column1 = 'value'
|
||||
AND IsActive = 1
|
||||
ORDER BY
|
||||
Column1 ASC;`,
|
||||
|
||||
insert: `-- Basic INSERT query
|
||||
INSERT INTO TableName (Column1, Column2, Column3)
|
||||
VALUES
|
||||
('Value1', 'Value2', 'Value3');`,
|
||||
|
||||
update: `-- Basic UPDATE query
|
||||
UPDATE TableName
|
||||
SET
|
||||
Column1 = 'NewValue1',
|
||||
Column2 = 'NewValue2'
|
||||
WHERE
|
||||
-- Add your conditions
|
||||
Id = 1;`,
|
||||
|
||||
delete: `-- Basic DELETE query
|
||||
DELETE FROM TableName
|
||||
WHERE
|
||||
-- Add your conditions
|
||||
Id = 1;`,
|
||||
|
||||
'create-procedure': `-- Create Stored Procedure
|
||||
CREATE PROCEDURE [dbo].[ProcedureName]
|
||||
@Parameter1 INT,
|
||||
@Parameter2 NVARCHAR(100)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
-- Add your logic here
|
||||
SELECT
|
||||
Column1,
|
||||
Column2
|
||||
FROM
|
||||
TableName
|
||||
WHERE
|
||||
Column1 = @Parameter1
|
||||
AND Column2 = @Parameter2;
|
||||
END
|
||||
GO`,
|
||||
|
||||
'create-view': `-- Create View
|
||||
CREATE VIEW [dbo].[ViewName]
|
||||
AS
|
||||
SELECT
|
||||
t1.Column1,
|
||||
t1.Column2,
|
||||
t2.Column3
|
||||
FROM
|
||||
TableName1 t1
|
||||
INNER JOIN TableName2 t2 ON t1.Id = t2.TableName1Id
|
||||
WHERE
|
||||
t1.IsActive = 1;
|
||||
GO`,
|
||||
|
||||
'create-scalar-function': `-- Create Scalar Function
|
||||
CREATE FUNCTION [dbo].[ScalarFunctionName]
|
||||
(
|
||||
@Parameter1 INT,
|
||||
@Parameter2 NVARCHAR(100)
|
||||
)
|
||||
RETURNS NVARCHAR(200)
|
||||
AS
|
||||
BEGIN
|
||||
DECLARE @Result NVARCHAR(200);
|
||||
|
||||
-- Add your logic here
|
||||
SELECT @Result = Column1 + ' ' + @Parameter2
|
||||
FROM TableName
|
||||
WHERE Id = @Parameter1;
|
||||
|
||||
RETURN @Result;
|
||||
END
|
||||
GO`,
|
||||
|
||||
'create-table-function': `-- Create Table-Valued Function
|
||||
CREATE FUNCTION [dbo].[TableFunctionName]
|
||||
(
|
||||
@Parameter1 INT,
|
||||
@Parameter2 NVARCHAR(100)
|
||||
)
|
||||
RETURNS TABLE
|
||||
AS
|
||||
RETURN
|
||||
(
|
||||
SELECT
|
||||
t.Column1,
|
||||
t.Column2,
|
||||
t.Column3,
|
||||
t.Column4
|
||||
FROM
|
||||
TableName t
|
||||
WHERE
|
||||
t.Id = @Parameter1
|
||||
AND t.Column2 LIKE '%' + @Parameter2 + '%'
|
||||
AND t.IsActive = 1
|
||||
)
|
||||
GO`,
|
||||
}
|
||||
|
||||
return templateContents[templateType] || templateContents['select']
|
||||
}
|
||||
|
||||
const TemplateDialog = ({ isOpen, onClose, onUseTemplate }: TemplateDialogProps) => {
|
||||
const { translate } = useLocalization()
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null)
|
||||
|
||||
const handleTemplateSelect = (template: Template) => {
|
||||
setSelectedTemplate(template)
|
||||
}
|
||||
|
||||
const handleUseTemplate = () => {
|
||||
if (selectedTemplate) {
|
||||
const content = getTemplateContent(selectedTemplate.type)
|
||||
onUseTemplate(content, selectedTemplate.type)
|
||||
onClose()
|
||||
setSelectedTemplate(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
onClose()
|
||||
setSelectedTemplate(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog isOpen={isOpen} onClose={handleClose} onRequestClose={handleClose} width={900}>
|
||||
<div className="flex flex-col h-[600px]">
|
||||
<h5 className="mb-4 text-lg font-semibold">
|
||||
{translate('::App.Platform.SelectTemplate')}
|
||||
</h5>
|
||||
|
||||
<div className="flex flex-1 gap-4 min-h-0">
|
||||
{/* Template List */}
|
||||
<div className="w-1/3 border rounded-lg p-2 overflow-y-auto">
|
||||
<div className="space-y-1">
|
||||
{templates.map((template) => (
|
||||
<div
|
||||
key={template.id}
|
||||
className={`flex items-start gap-3 p-3 rounded-lg cursor-pointer transition-colors ${
|
||||
selectedTemplate?.id === template.id
|
||||
? 'bg-blue-100 dark:bg-blue-900 border-2 border-blue-500'
|
||||
: 'bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 border-2 border-transparent'
|
||||
}`}
|
||||
onClick={() => handleTemplateSelect(template)}
|
||||
>
|
||||
<FaCode
|
||||
className={`text-lg mt-1 ${
|
||||
selectedTemplate?.id === template.id
|
||||
? 'text-blue-600 dark:text-blue-400'
|
||||
: 'text-gray-500'
|
||||
}`}
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm">{template.label}</div>
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
||||
{template.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Template Preview */}
|
||||
<div className="flex-1 border rounded-lg p-4 overflow-hidden flex flex-col">
|
||||
{selectedTemplate ? (
|
||||
<>
|
||||
<div className="mb-3">
|
||||
<h6 className="font-semibold text-sm mb-1">{selectedTemplate.label}</h6>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||
{selectedTemplate.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto bg-gray-900 rounded p-3">
|
||||
<pre className="text-sm text-gray-100 font-mono">
|
||||
{getTemplateContent(selectedTemplate.type)}
|
||||
</pre>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full text-gray-500 dark:text-gray-400">
|
||||
{translate('::App.Platform.SelectATemplateToPreview')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex justify-end gap-2 mt-4 pt-4 border-t">
|
||||
<Button variant="plain" onClick={handleClose}>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
onClick={handleUseTemplate}
|
||||
disabled={!selectedTemplate}
|
||||
color="blue-600"
|
||||
>
|
||||
{translate('::App.Platform.UseTemplate')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default TemplateDialog
|
||||
Loading…
Reference in a new issue