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 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 HostPrefix { get; set; } = "T";
|
||||||
public static string? DbSchema { get; set; } = null;
|
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;
|
namespace Sozsoft.Platform.DeveloperKit;
|
||||||
|
|
||||||
public interface ICustomEntityAppService : ICrudAppService<
|
public interface ISqlTableAppService : ICrudAppService<
|
||||||
CustomEntityDto,
|
SqlTableDto,
|
||||||
Guid,
|
Guid,
|
||||||
PagedAndSortedResultRequestDto,
|
PagedAndSortedResultRequestDto,
|
||||||
CreateUpdateCustomEntityDto>
|
CreateUpdateSqlTableDto>
|
||||||
{
|
{
|
||||||
Task<List<CustomEntityDto>> GetActiveEntitiesAsync();
|
Task<List<SqlTableDto>> GetActiveEntitiesAsync();
|
||||||
Task<CustomEntityDto> ToggleActiveStatusAsync(Guid id);
|
Task<SqlTableDto> ToggleActiveStatusAsync(Guid id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4,7 +4,7 @@ using Volo.Abp.Application.Dtos;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.DeveloperKit;
|
namespace Sozsoft.Platform.DeveloperKit;
|
||||||
|
|
||||||
public class CustomEntityDto : FullAuditedEntityDto<Guid>
|
public class SqlTableDto : FullAuditedEntityDto<Guid>
|
||||||
{
|
{
|
||||||
public string Menu { get; set; } = string.Empty;
|
public string Menu { get; set; } = string.Empty;
|
||||||
public string Name { 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 IsActive { get; set; } = true;
|
||||||
public bool IsFullAuditedEntity { get; set; } = true;
|
public bool IsFullAuditedEntity { get; set; } = true;
|
||||||
public bool IsMultiTenant { get; set; } = false;
|
public bool IsMultiTenant { get; set; } = false;
|
||||||
public string MigrationStatus { get; set; } = "Askıda";
|
public string EndpointStatus { get; set; } = "pending";
|
||||||
public Guid? MigrationId { get; set; }
|
|
||||||
public string EndpointStatus { get; set; } = "Askıda";
|
|
||||||
|
|
||||||
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 Menu { get; set; } = string.Empty;
|
||||||
public string Name { 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 IsFullAuditedEntity { get; set; } = true;
|
||||||
public bool IsMultiTenant { get; set; } = false;
|
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 Guid EntityId { get; set; }
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
@ -48,7 +46,7 @@ public class EntityFieldDto : FullAuditedEntityDto<Guid>
|
||||||
public int DisplayOrder { get; set; } = 0;
|
public int DisplayOrder { get; set; } = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreateUpdateCustomEntityFieldDto
|
public class CreateUpdateSqlTableFieldDto
|
||||||
{
|
{
|
||||||
public Guid? Id { get; set; }
|
public Guid? Id { get; set; }
|
||||||
|
|
||||||
|
|
@ -18,19 +18,16 @@ public class CrudEndpointGenerateAppService : CrudAppService<
|
||||||
PagedAndSortedResultRequestDto,
|
PagedAndSortedResultRequestDto,
|
||||||
CreateUpdateCrudEndpointDto>, ICrudEndpointAppService
|
CreateUpdateCrudEndpointDto>, ICrudEndpointAppService
|
||||||
{
|
{
|
||||||
private readonly IRepository<CustomEntity, Guid> _entityRepository;
|
private readonly IRepository<SqlTable, Guid> _entityRepository;
|
||||||
private readonly IRepository<CrudMigration, Guid> _migrationRepository;
|
|
||||||
private readonly IRepository<CrudEndpoint, Guid> _endpointRepository;
|
private readonly IRepository<CrudEndpoint, Guid> _endpointRepository;
|
||||||
|
|
||||||
public CrudEndpointGenerateAppService(
|
public CrudEndpointGenerateAppService(
|
||||||
IRepository<CrudEndpoint, Guid> repository,
|
IRepository<CrudEndpoint, Guid> repository,
|
||||||
IRepository<CustomEntity, Guid> entityRepository,
|
IRepository<SqlTable, Guid> entityRepository,
|
||||||
IRepository<CrudMigration, Guid> migrationRepository,
|
|
||||||
IRepository<CrudEndpoint, Guid> endpointRepository)
|
IRepository<CrudEndpoint, Guid> endpointRepository)
|
||||||
: base(repository)
|
: base(repository)
|
||||||
{
|
{
|
||||||
_entityRepository = entityRepository;
|
_entityRepository = entityRepository;
|
||||||
_migrationRepository = migrationRepository;
|
|
||||||
_endpointRepository = endpointRepository;
|
_endpointRepository = endpointRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,17 +56,6 @@ public class CrudEndpointGenerateAppService : CrudAppService<
|
||||||
throw new Exception($"Entity with ID {entityId} not found");
|
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
|
// CRUD endpointleri oluştur
|
||||||
var endpoints = new List<CrudEndpoint>();
|
var endpoints = new List<CrudEndpoint>();
|
||||||
var entityName = entity.Name;
|
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()
|
public DeveloperKitAutoMapperProfile()
|
||||||
{
|
{
|
||||||
// CustomEntity mappings
|
// SqlTable mappings
|
||||||
CreateMap<CustomEntity, CustomEntityDto>();
|
CreateMap<SqlTable, SqlTableDto>();
|
||||||
CreateMap<CreateUpdateCustomEntityDto, CustomEntity>();
|
CreateMap<CreateUpdateSqlTableDto, SqlTable>();
|
||||||
|
|
||||||
// EntityField mappings
|
// EntityField mappings
|
||||||
CreateMap<CustomEntityField, EntityFieldDto>();
|
CreateMap<SqlTableField, SqlTableFieldDto>();
|
||||||
CreateMap<CreateUpdateCustomEntityFieldDto, CustomEntityField>();
|
CreateMap<CreateUpdateSqlTableFieldDto, SqlTableField>();
|
||||||
|
|
||||||
// CustomComponent mappings
|
// CustomComponent mappings
|
||||||
CreateMap<CustomComponent, CustomComponentDto>();
|
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));
|
.ForMember(dest => dest.EntityDisplayName, opt => opt.MapFrom(src => src.Entity != null ? src.Entity.DisplayName : null));
|
||||||
CreateMap<CreateUpdateCrudEndpointDto, CrudEndpoint>();
|
CreateMap<CreateUpdateCrudEndpointDto, CrudEndpoint>();
|
||||||
|
|
||||||
// Migration mappings
|
|
||||||
CreateMap<CrudMigration, CrudMigrationDto>();
|
|
||||||
CreateMap<CreateUpdateCrudMigrationDto, CrudMigration>();
|
|
||||||
|
|
||||||
CreateMap<DynamicService, DynamicServiceDto>()
|
CreateMap<DynamicService, DynamicServiceDto>()
|
||||||
.ForMember(dest => dest.CompilationStatus, opt => opt.MapFrom(src => src.CompilationStatus.ToString()));
|
.ForMember(dest => dest.CompilationStatus, opt => opt.MapFrom(src => src.CompilationStatus.ToString()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,35 +9,31 @@ using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Platform.Api.Application;
|
namespace Platform.Api.Application;
|
||||||
|
|
||||||
public class CustomEntityAppService : CrudAppService<
|
public class SqlTableAppService : CrudAppService<
|
||||||
CustomEntity,
|
SqlTable,
|
||||||
CustomEntityDto,
|
SqlTableDto,
|
||||||
Guid,
|
Guid,
|
||||||
PagedAndSortedResultRequestDto,
|
PagedAndSortedResultRequestDto,
|
||||||
CreateUpdateCustomEntityDto>, ICustomEntityAppService
|
CreateUpdateSqlTableDto>, ISqlTableAppService
|
||||||
{
|
{
|
||||||
private readonly IRepository<CustomEntity, Guid> _repository;
|
private readonly IRepository<SqlTable, Guid> _repository;
|
||||||
private readonly IRepository<CrudMigration, Guid> _migrationRepository;
|
private readonly IRepository<SqlTableField, Guid> _fieldRepository;
|
||||||
private readonly IRepository<CrudEndpoint, Guid> _endpointRepository;
|
private readonly IRepository<CrudEndpoint, Guid> _endpointRepository;
|
||||||
private readonly IRepository<CustomEntityField, Guid> _fieldRepository;
|
|
||||||
|
|
||||||
public CustomEntityAppService(
|
public SqlTableAppService(
|
||||||
IRepository<CustomEntity, Guid> repository,
|
IRepository<SqlTable, Guid> repository,
|
||||||
IRepository<CrudMigration, Guid> migrationRepository,
|
IRepository<SqlTableField, Guid> fieldRepository,
|
||||||
IRepository<CrudEndpoint, Guid> endpointRepository,
|
IRepository<CrudEndpoint, Guid> endpointRepository) : base(repository)
|
||||||
IRepository<CustomEntityField, Guid> fieldRepository) : base(repository)
|
|
||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_migrationRepository = migrationRepository;
|
|
||||||
_endpointRepository = endpointRepository;
|
|
||||||
_fieldRepository = fieldRepository;
|
_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 query = await _repository.GetQueryableAsync();
|
||||||
var fullQuery = query.Include(x => x.Fields.OrderBy(f => f.DisplayOrder));
|
var fullQuery = query.Include(x => x.Fields.OrderBy(f => f.DisplayOrder));
|
||||||
|
|
@ -50,12 +46,12 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
.Take(input.MaxResultCount)
|
.Take(input.MaxResultCount)
|
||||||
.ToListAsync();
|
.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 query = await _repository.GetQueryableAsync();
|
||||||
var entity = await query
|
var entity = await query
|
||||||
|
|
@ -65,10 +61,10 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
if (entity == null)
|
if (entity == null)
|
||||||
throw new EntityNotFoundException($"CustomEntity with id {id} not found");
|
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 query = await _repository.GetQueryableAsync();
|
||||||
var entities = await query
|
var entities = await query
|
||||||
|
|
@ -76,10 +72,10 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
.Where(x => x.IsActive)
|
.Where(x => x.IsActive)
|
||||||
.ToListAsync();
|
.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 query = await _repository.GetQueryableAsync();
|
||||||
var entity = await query
|
var entity = await query
|
||||||
|
|
@ -92,10 +88,10 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
entity.IsActive = !entity.IsActive;
|
entity.IsActive = !entity.IsActive;
|
||||||
await _repository.UpdateAsync(entity, autoSave: true);
|
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 query = await _repository.GetQueryableAsync();
|
||||||
var entity = await query
|
var entity = await query
|
||||||
|
|
@ -103,20 +99,7 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
.FirstOrDefaultAsync(e => e.Id == id);
|
.FirstOrDefaultAsync(e => e.Id == id);
|
||||||
|
|
||||||
if (entity == null)
|
if (entity == null)
|
||||||
throw new EntityNotFoundException(typeof(CustomEntity), id);
|
throw new EntityNotFoundException(typeof(SqlTable), 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
entity.Menu = input.Menu;
|
entity.Menu = input.Menu;
|
||||||
entity.Name = input.Name;
|
entity.Name = input.Name;
|
||||||
|
|
@ -127,12 +110,12 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
entity.IsFullAuditedEntity = input.IsFullAuditedEntity;
|
entity.IsFullAuditedEntity = input.IsFullAuditedEntity;
|
||||||
entity.IsMultiTenant = input.IsMultiTenant;
|
entity.IsMultiTenant = input.IsMultiTenant;
|
||||||
|
|
||||||
var updatedFields = new List<CustomEntityField>();
|
var updatedFields = new List<SqlTableField>();
|
||||||
|
|
||||||
for (int i = 0; i < input.Fields.Count; i++)
|
for (int i = 0; i < input.Fields.Count; i++)
|
||||||
{
|
{
|
||||||
var dtoField = input.Fields[i];
|
var dtoField = input.Fields[i];
|
||||||
CustomEntityField? existingField = null;
|
SqlTableField? existingField = null;
|
||||||
|
|
||||||
if (dtoField.Id.HasValue)
|
if (dtoField.Id.HasValue)
|
||||||
{
|
{
|
||||||
|
|
@ -154,7 +137,7 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var newField = new CustomEntityField
|
var newField = new SqlTableField
|
||||||
{
|
{
|
||||||
EntityId = entity.Id,
|
EntityId = entity.Id,
|
||||||
Name = dtoField.Name,
|
Name = dtoField.Name,
|
||||||
|
|
@ -184,13 +167,13 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
|
|
||||||
await _repository.UpdateAsync(entity, autoSave: true);
|
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
|
// Entity oluştur
|
||||||
var entity = new CustomEntity
|
var entity = new SqlTable
|
||||||
{
|
{
|
||||||
Menu = input.Menu,
|
Menu = input.Menu,
|
||||||
Name = input.Name,
|
Name = input.Name,
|
||||||
|
|
@ -200,14 +183,13 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
IsActive = input.IsActive,
|
IsActive = input.IsActive,
|
||||||
IsFullAuditedEntity = input.IsFullAuditedEntity,
|
IsFullAuditedEntity = input.IsFullAuditedEntity,
|
||||||
IsMultiTenant = input.IsMultiTenant,
|
IsMultiTenant = input.IsMultiTenant,
|
||||||
MigrationStatus = "Askıda"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fields ekle - sıralama ile
|
// Fields ekle - sıralama ile
|
||||||
for (int i = 0; i < input.Fields.Count; i++)
|
for (int i = 0; i < input.Fields.Count; i++)
|
||||||
{
|
{
|
||||||
var fieldDto = input.Fields[i];
|
var fieldDto = input.Fields[i];
|
||||||
var field = new CustomEntityField
|
var field = new SqlTableField
|
||||||
{
|
{
|
||||||
EntityId = entity.Id,
|
EntityId = entity.Id,
|
||||||
Name = fieldDto.Name,
|
Name = fieldDto.Name,
|
||||||
|
|
@ -224,10 +206,10 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
|
|
||||||
entity = await _repository.InsertAsync(entity, autoSave: true);
|
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)
|
if (existingFields.Count != inputFields.Count)
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -252,31 +234,5 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
|
|
||||||
return false;
|
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",
|
"en": "Custom Entity",
|
||||||
"tr": "Özel Varlık"
|
"tr": "Özel Varlık"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.DeveloperKit.Migrations",
|
|
||||||
"en": "Crud Migrations",
|
|
||||||
"tr": "Crud Geçişleri"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.DeveloperKit.CrudEndpoints",
|
"key": "App.DeveloperKit.CrudEndpoints",
|
||||||
|
|
|
||||||
|
|
@ -283,84 +283,49 @@
|
||||||
{
|
{
|
||||||
"key": "admin.sqlQueryManager",
|
"key": "admin.sqlQueryManager",
|
||||||
"path": "/admin/sqlQueryManager",
|
"path": "/admin/sqlQueryManager",
|
||||||
"componentPath": "@/views/sqlQueryManager/SqlQueryManager",
|
"componentPath": "@/views/developerKit/SqlQueryManager",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": ["App.SqlQueryManager"]
|
"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",
|
"key": "admin.developerkit.endpoints",
|
||||||
"path": "/admin/developerkit/endpoints",
|
"path": "/admin/developerkit/endpoints",
|
||||||
"componentPath": "@/views/developerKit/CrudEndpointPage",
|
"componentPath": "@/views/developerKit/CrudEndpointManager",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": ["App.DeveloperKit.CrudEndpoints"]
|
"authority": ["App.DeveloperKit.CrudEndpoints"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.developerkit.dynamic-services",
|
"key": "admin.developerkit.dynamic-services",
|
||||||
"path": "/admin/developerkit/dynamic-services",
|
"path": "/admin/developerkit/dynamic-services",
|
||||||
"componentPath": "@/views/developerKit/DynamicServicePage",
|
"componentPath": "@/views/developerKit/DynamicServiceEditor",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": ["App.DeveloperKit.DynamicServices"]
|
"authority": ["App.DeveloperKit.DynamicServices"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.developerkit.components",
|
"key": "admin.developerkit.components",
|
||||||
"path": "/admin/developerkit/components",
|
"path": "/admin/developerkit/components",
|
||||||
"componentPath": "@/views/developerKit/ComponentPage",
|
"componentPath": "@/views/developerKit/ComponentManagerPage",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": ["App.DeveloperKit.Components"]
|
"authority": ["App.DeveloperKit.Components"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.developerkit.components.new",
|
"key": "admin.developerkit.components.new",
|
||||||
"path": "/admin/developerkit/components/new",
|
"path": "/admin/developerkit/components/new",
|
||||||
"componentPath": "@/views/developerKit/ComponentDetailPage",
|
"componentPath": "@/views/developerKit/ComponentEditorPage",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": ["App.DeveloperKit.Components"]
|
"authority": ["App.DeveloperKit.Components"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.developerkit.components.view",
|
"key": "admin.developerkit.components.view",
|
||||||
"path": "/admin/developerkit/components/view/:id",
|
"path": "/admin/developerkit/components/view/:id",
|
||||||
"componentPath": "@/views/developerKit/ComponentDetailPage",
|
"componentPath": "@/views/developerKit/ComponentEditorPage",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": ["App.DeveloperKit.Components"]
|
"authority": ["App.DeveloperKit.Components"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.developerkit.components.edit",
|
"key": "admin.developerkit.components.edit",
|
||||||
"path": "/admin/developerkit/components/edit/:id",
|
"path": "/admin/developerkit/components/edit/:id",
|
||||||
"componentPath": "@/views/developerKit/CodePage",
|
"componentPath": "@/views/developerKit/CodeLayout",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": ["App.DeveloperKit.Components"]
|
"authority": ["App.DeveloperKit.Components"]
|
||||||
},
|
},
|
||||||
|
|
@ -971,39 +936,19 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.DeveloperKit",
|
"ParentCode": "App.DeveloperKit",
|
||||||
"Code": "App.DeveloperKit.Dashboard",
|
"Code": "App.SqlQueryManager",
|
||||||
"DisplayName": "App.Coordinator.Classroom.Dashboard",
|
"DisplayName": "App.SqlQueryManager",
|
||||||
"Order": 1,
|
"Order": 1,
|
||||||
"Url": "/admin/developerkit",
|
"Url": "/admin/sqlQueryManager",
|
||||||
"Icon": "FcBinoculars",
|
"Icon": "FaDatabase",
|
||||||
"RequiredPermissionName": "App.DeveloperKit",
|
"RequiredPermissionName": "App.SqlQueryManager",
|
||||||
"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",
|
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.DeveloperKit",
|
"ParentCode": "App.DeveloperKit",
|
||||||
"Code": "App.DeveloperKit.CrudEndpoints",
|
"Code": "App.DeveloperKit.CrudEndpoints",
|
||||||
"DisplayName": "App.DeveloperKit.CrudEndpoints",
|
"DisplayName": "App.DeveloperKit.CrudEndpoints",
|
||||||
"Order": 4,
|
"Order": 2,
|
||||||
"Url": "/admin/developerkit/endpoints",
|
"Url": "/admin/developerkit/endpoints",
|
||||||
"Icon": "FcOrgUnit",
|
"Icon": "FcOrgUnit",
|
||||||
"RequiredPermissionName": "App.DeveloperKit.CrudEndpoints",
|
"RequiredPermissionName": "App.DeveloperKit.CrudEndpoints",
|
||||||
|
|
@ -1013,7 +958,7 @@
|
||||||
"ParentCode": "App.DeveloperKit",
|
"ParentCode": "App.DeveloperKit",
|
||||||
"Code": "App.DeveloperKit.CustomEndpoints",
|
"Code": "App.DeveloperKit.CustomEndpoints",
|
||||||
"DisplayName": "App.DeveloperKit.CustomEndpoints",
|
"DisplayName": "App.DeveloperKit.CustomEndpoints",
|
||||||
"Order": 5,
|
"Order": 3,
|
||||||
"Url": "/admin/list/App.DeveloperKit.CustomEndpoints",
|
"Url": "/admin/list/App.DeveloperKit.CustomEndpoints",
|
||||||
"Icon": "FcMindMap",
|
"Icon": "FcMindMap",
|
||||||
"RequiredPermissionName": "App.DeveloperKit.CustomEndpoints",
|
"RequiredPermissionName": "App.DeveloperKit.CustomEndpoints",
|
||||||
|
|
@ -1023,7 +968,7 @@
|
||||||
"ParentCode": "App.DeveloperKit",
|
"ParentCode": "App.DeveloperKit",
|
||||||
"Code": "App.DeveloperKit.Components",
|
"Code": "App.DeveloperKit.Components",
|
||||||
"DisplayName": "App.DeveloperKit.Components",
|
"DisplayName": "App.DeveloperKit.Components",
|
||||||
"Order": 6,
|
"Order": 4,
|
||||||
"Url": "/admin/developerkit/components",
|
"Url": "/admin/developerkit/components",
|
||||||
"Icon": "FcBiohazard",
|
"Icon": "FcBiohazard",
|
||||||
"RequiredPermissionName": "App.DeveloperKit.Components",
|
"RequiredPermissionName": "App.DeveloperKit.Components",
|
||||||
|
|
@ -1033,27 +978,17 @@
|
||||||
"ParentCode": "App.DeveloperKit",
|
"ParentCode": "App.DeveloperKit",
|
||||||
"Code": "App.DeveloperKit.DynamicServices",
|
"Code": "App.DeveloperKit.DynamicServices",
|
||||||
"DisplayName": "App.DeveloperKit.DynamicServices",
|
"DisplayName": "App.DeveloperKit.DynamicServices",
|
||||||
"Order": 7,
|
"Order": 5,
|
||||||
"Url": "/admin/developerkit/dynamic-services",
|
"Url": "/admin/developerkit/dynamic-services",
|
||||||
"Icon": "FcCommandLine",
|
"Icon": "FcCommandLine",
|
||||||
"RequiredPermissionName": "App.DeveloperKit.DynamicServices",
|
"RequiredPermissionName": "App.DeveloperKit.DynamicServices",
|
||||||
"IsDisabled": false
|
"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",
|
"ParentCode": "App.DeveloperKit",
|
||||||
"Code": "App.Listforms.Wizard",
|
"Code": "App.Listforms.Wizard",
|
||||||
"DisplayName": "App.Listforms.Wizard",
|
"DisplayName": "App.Listforms.Wizard",
|
||||||
"Order": 9,
|
"Order": 6,
|
||||||
"Url": "/admin/listform/wizard",
|
"Url": "/admin/listform/wizard",
|
||||||
"Icon": "FcFlashAuto",
|
"Icon": "FcFlashAuto",
|
||||||
"RequiredPermissionName": "App.Listforms.Wizard",
|
"RequiredPermissionName": "App.Listforms.Wizard",
|
||||||
|
|
|
||||||
|
|
@ -2233,24 +2233,6 @@
|
||||||
"MultiTenancySide": 3,
|
"MultiTenancySide": 3,
|
||||||
"MenuGroup": "Erp|Kurs"
|
"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",
|
"GroupName": "App.Administration",
|
||||||
"Name": "App.DeveloperKit.CrudEndpoints",
|
"Name": "App.DeveloperKit.CrudEndpoints",
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,13 @@ public enum TableNameEnum
|
||||||
ForumCategory,
|
ForumCategory,
|
||||||
ForumTopic,
|
ForumTopic,
|
||||||
ForumPost,
|
ForumPost,
|
||||||
|
SqlTable,
|
||||||
|
SqlTableField,
|
||||||
|
SqlView,
|
||||||
|
SqlStoredProcedure,
|
||||||
|
SqlFunction,
|
||||||
|
SqlQuery,
|
||||||
CrudEndpoint,
|
CrudEndpoint,
|
||||||
CrudMigration,
|
|
||||||
CustomEntity,
|
|
||||||
CustomEntityField,
|
|
||||||
CustomEndpoint,
|
CustomEndpoint,
|
||||||
CustomComponent,
|
CustomComponent,
|
||||||
DynamicService,
|
DynamicService,
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,8 @@ public static class TableNameResolver
|
||||||
{ nameof(TableNameEnum.Branch), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
{ nameof(TableNameEnum.Branch), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||||
{ nameof(TableNameEnum.BranchUsers), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
{ nameof(TableNameEnum.BranchUsers), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||||
{ nameof(TableNameEnum.GlobalSearch), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
{ nameof(TableNameEnum.GlobalSearch), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||||
{ nameof(TableNameEnum.CustomEntity), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
{ nameof(TableNameEnum.SqlTable), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||||
{ nameof(TableNameEnum.CustomEntityField), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
{ nameof(TableNameEnum.SqlTableField), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||||
{ nameof(TableNameEnum.CrudMigration), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
|
||||||
{ nameof(TableNameEnum.CrudEndpoint), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
{ nameof(TableNameEnum.CrudEndpoint), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||||
{ nameof(TableNameEnum.CustomEndpoint), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
{ nameof(TableNameEnum.CustomEndpoint), (TablePrefix.TenantByName, MenuPrefix.Saas) },
|
||||||
{ nameof(TableNameEnum.CustomComponent), (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 string CsharpCode { get; set; } = string.Empty;
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
|
|
||||||
// Foreign key to CustomEntity
|
// Foreign key to SqlTable
|
||||||
public Guid EntityId { get; set; }
|
public Guid EntityId { get; set; }
|
||||||
public virtual CustomEntity Entity { get; set; } = null!;
|
public virtual SqlTable Entity { get; set; } = null!;
|
||||||
public CrudEndpoint()
|
public CrudEndpoint()
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid();
|
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;
|
namespace Sozsoft.Platform.Entities;
|
||||||
|
|
||||||
public class CustomEntity : FullAuditedEntity<Guid>, IMultiTenant
|
public class SqlTable : FullAuditedEntity<Guid>, IMultiTenant
|
||||||
{
|
{
|
||||||
public virtual Guid? TenantId { get; protected set; }
|
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 IsActive { get; set; } = true;
|
||||||
public bool IsFullAuditedEntity { get; set; } = true;
|
public bool IsFullAuditedEntity { get; set; } = true;
|
||||||
public bool IsMultiTenant { get; set; } = false;
|
public bool IsMultiTenant { get; set; } = false;
|
||||||
public string MigrationStatus { get; set; } = "Askıda";
|
public string EndpointStatus { get; set; } = "pending"; // "pending" | "applied" | "failed"
|
||||||
public Guid? MigrationId { get; set; }
|
|
||||||
public string EndpointStatus { get; set; } = "Askıda"; // "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
|
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 Guid EntityId { get; set; } = Guid.NewGuid();
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
@ -41,9 +39,9 @@ public class CustomEntityField : FullAuditedEntity<Guid>
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public int DisplayOrder { get; set; } = 0;
|
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
|
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
|
public class DynamicEntityManager : IDynamicEntityManager
|
||||||
{
|
{
|
||||||
private readonly IDbContextProvider<PlatformDbContext> _dbContextProvider;
|
private readonly IDbContextProvider<PlatformDbContext> _dbContextProvider;
|
||||||
private readonly IRepository<CustomEntity, Guid> _customEntityRepository;
|
private readonly IRepository<SqlTable, Guid> _sqlTableRepository;
|
||||||
|
|
||||||
public DynamicEntityManager(
|
public DynamicEntityManager(
|
||||||
IDbContextProvider<PlatformDbContext> dbContextProvider,
|
IDbContextProvider<PlatformDbContext> dbContextProvider,
|
||||||
IRepository<CustomEntity, Guid> customEntityRepository)
|
IRepository<SqlTable, Guid> sqlTableRepository)
|
||||||
{
|
{
|
||||||
_dbContextProvider = dbContextProvider;
|
_dbContextProvider = dbContextProvider;
|
||||||
_customEntityRepository = customEntityRepository;
|
_sqlTableRepository = sqlTableRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<object>?> GetEntityListAsync(string entityName)
|
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());
|
var entity = await queryable.FirstOrDefaultAsync(x => x.Name.ToLower() == entityName.ToLower());
|
||||||
|
|
||||||
return entity ?? throw new UserFriendlyException($"Entity '{entityName}' not found.");
|
return entity ?? throw new UserFriendlyException($"Entity '{entityName}' not found.");
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,8 @@ public class PlatformDbContext :
|
||||||
public DbSet<ForumCategory> ForumCategories { get; set; }
|
public DbSet<ForumCategory> ForumCategories { get; set; }
|
||||||
public DbSet<ForumTopic> ForumTopics { get; set; }
|
public DbSet<ForumTopic> ForumTopics { get; set; }
|
||||||
public DbSet<ForumPost> ForumPosts { get; set; }
|
public DbSet<ForumPost> ForumPosts { get; set; }
|
||||||
public DbSet<CustomEntity> CustomEntities { get; set; }
|
public DbSet<SqlTable> CustomEntities { get; set; }
|
||||||
public DbSet<CustomEntityField> EntityFields { get; set; }
|
public DbSet<SqlTableField> EntityFields { get; set; }
|
||||||
public DbSet<CrudMigration> Migrations { get; set; }
|
|
||||||
public DbSet<CrudEndpoint> GeneratedEndpoints { get; set; }
|
public DbSet<CrudEndpoint> GeneratedEndpoints { get; set; }
|
||||||
public DbSet<CustomEndpoint> CustomEndpoints { get; set; }
|
public DbSet<CustomEndpoint> CustomEndpoints { get; set; }
|
||||||
public DbSet<CustomComponent> CustomComponents { get; set; }
|
public DbSet<CustomComponent> CustomComponents { get; set; }
|
||||||
|
|
@ -516,9 +515,9 @@ public class PlatformDbContext :
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.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.ConfigureByConvention();
|
||||||
|
|
||||||
b.HasKey(x => x.Id);
|
b.HasKey(x => x.Id);
|
||||||
|
|
@ -526,7 +525,6 @@ public class PlatformDbContext :
|
||||||
b.Property(x => x.DisplayName).IsRequired().HasMaxLength(128);
|
b.Property(x => x.DisplayName).IsRequired().HasMaxLength(128);
|
||||||
b.Property(x => x.TableName).IsRequired().HasMaxLength(128);
|
b.Property(x => x.TableName).IsRequired().HasMaxLength(128);
|
||||||
b.Property(x => x.Description).HasMaxLength(512);
|
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.EndpointStatus).IsRequired().HasMaxLength(64);
|
||||||
b.Property(x => x.Menu).IsRequired().HasMaxLength(128);
|
b.Property(x => x.Menu).IsRequired().HasMaxLength(128);
|
||||||
|
|
||||||
|
|
@ -536,9 +534,9 @@ public class PlatformDbContext :
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.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.ConfigureByConvention();
|
||||||
|
|
||||||
b.HasKey(x => x.Id);
|
b.HasKey(x => x.Id);
|
||||||
|
|
@ -548,24 +546,6 @@ public class PlatformDbContext :
|
||||||
b.Property(x => x.DefaultValue).HasMaxLength(256);
|
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 =>
|
builder.Entity<CrudEndpoint>(b =>
|
||||||
{
|
{
|
||||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.CrudEndpoint)), Prefix.DbSchema);
|
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.CrudEndpoint)), Prefix.DbSchema);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Sozsoft.Platform.Migrations
|
namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20260301173343_Initial")]
|
[Migration("20260301203437_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -1419,67 +1419,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_T_CrudEndpoint", (string)null);
|
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 =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Currency", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
|
|
@ -1688,171 +1627,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_T_CustomEndpoint", (string)null);
|
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 =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.DataSource", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -4001,6 +3775,163 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Adm_T_SkillType", (string)null);
|
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 =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Uom", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
|
|
@ -4663,7 +4594,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
b.HasIndex("SchemaName", "FunctionName");
|
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 =>
|
modelBuilder.Entity("Sozsoft.SqlQueryManager.Domain.Entities.SqlQuery", b =>
|
||||||
|
|
@ -4763,7 +4694,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
b.HasIndex("Status");
|
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 =>
|
modelBuilder.Entity("Sozsoft.SqlQueryManager.Domain.Entities.SqlStoredProcedure", b =>
|
||||||
|
|
@ -4861,7 +4792,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
b.HasIndex("SchemaName", "ProcedureName");
|
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 =>
|
modelBuilder.Entity("Sozsoft.SqlQueryManager.Domain.Entities.SqlView", b =>
|
||||||
|
|
@ -4958,7 +4889,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
b.HasIndex("SchemaName", "ViewName");
|
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 =>
|
modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b =>
|
||||||
|
|
@ -6879,7 +6810,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CrudEndpoint", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.CrudEndpoint", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Sozsoft.Platform.Entities.CustomEntity", "Entity")
|
b.HasOne("Sozsoft.Platform.Entities.SqlTable", "Entity")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("EntityId")
|
.HasForeignKey("EntityId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
|
@ -6888,28 +6819,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Navigation("Entity");
|
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 =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.District", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Sozsoft.Platform.Entities.City", null)
|
b.HasOne("Sozsoft.Platform.Entities.City", null)
|
||||||
|
|
@ -7000,6 +6909,17 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Navigation("SkillType");
|
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 =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Uom", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Sozsoft.Platform.Entities.UomCategory", "UomCategory")
|
b.HasOne("Sozsoft.Platform.Entities.UomCategory", "UomCategory")
|
||||||
|
|
@ -7222,11 +7142,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Navigation("Cities");
|
b.Navigation("Cities");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CustomEntity", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Fields");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Items");
|
b.Navigation("Items");
|
||||||
|
|
@ -7244,6 +7159,11 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Navigation("Skills");
|
b.Navigation("Skills");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sozsoft.Platform.Entities.SqlTable", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Fields");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.UomCategory", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.UomCategory", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Uoms");
|
b.Navigation("Uoms");
|
||||||
|
|
@ -1362,36 +1362,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
table.PrimaryKey("PK_Sas_T_CustomEndpoint", x => x.Id);
|
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(
|
migrationBuilder.CreateTable(
|
||||||
name: "Sas_T_DynamicService",
|
name: "Sas_T_DynamicService",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
|
@ -1531,7 +1501,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Sqm_T_SqlFunction",
|
name: "Sas_T_SqlFunction",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
|
@ -1559,11 +1529,11 @@ namespace Sozsoft.Platform.Migrations
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Sqm_T_SqlFunction", x => x.Id);
|
table.PrimaryKey("PK_Sas_T_SqlFunction", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Sqm_T_SqlQuery",
|
name: "Sas_T_SqlQuery",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
|
@ -1590,11 +1560,11 @@ namespace Sozsoft.Platform.Migrations
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Sqm_T_SqlQuery", x => x.Id);
|
table.PrimaryKey("PK_Sas_T_SqlQuery", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Sqm_T_SqlStoredProcedure",
|
name: "Sas_T_SqlStoredProcedure",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
|
@ -1620,11 +1590,39 @@ namespace Sozsoft.Platform.Migrations
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Sqm_T_SqlStoredProcedure", x => x.Id);
|
table.PrimaryKey("PK_Sas_T_SqlStoredProcedure", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
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
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
|
@ -1650,7 +1648,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Sqm_T_SqlView", x => x.Id);
|
table.PrimaryKey("PK_Sas_T_SqlView", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
|
|
@ -2376,100 +2374,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
onDelete: ReferentialAction.Restrict);
|
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(
|
migrationBuilder.CreateTable(
|
||||||
name: "Sas_T_ReportTemplate",
|
name: "Sas_T_ReportTemplate",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
|
@ -2500,6 +2404,71 @@ namespace Sozsoft.Platform.Migrations
|
||||||
onDelete: ReferentialAction.Cascade);
|
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(
|
migrationBuilder.CreateTable(
|
||||||
name: "AbpEntityPropertyChanges",
|
name: "AbpEntityPropertyChanges",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
|
@ -3148,104 +3117,99 @@ namespace Sozsoft.Platform.Migrations
|
||||||
table: "Sas_T_CrudEndpoint",
|
table: "Sas_T_CrudEndpoint",
|
||||||
column: "EntityId");
|
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(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sas_T_ReportTemplate_CategoryId",
|
name: "IX_Sas_T_ReportTemplate_CategoryId",
|
||||||
table: "Sas_T_ReportTemplate",
|
table: "Sas_T_ReportTemplate",
|
||||||
column: "CategoryId");
|
column: "CategoryId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlFunction_DataSourceCode",
|
name: "IX_Sas_T_SqlFunction_DataSourceCode",
|
||||||
table: "Sqm_T_SqlFunction",
|
table: "Sas_T_SqlFunction",
|
||||||
column: "DataSourceCode");
|
column: "DataSourceCode");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlFunction_FunctionType",
|
name: "IX_Sas_T_SqlFunction_FunctionType",
|
||||||
table: "Sqm_T_SqlFunction",
|
table: "Sas_T_SqlFunction",
|
||||||
column: "FunctionType");
|
column: "FunctionType");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlFunction_IsDeployed",
|
name: "IX_Sas_T_SqlFunction_IsDeployed",
|
||||||
table: "Sqm_T_SqlFunction",
|
table: "Sas_T_SqlFunction",
|
||||||
column: "IsDeployed");
|
column: "IsDeployed");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlFunction_SchemaName_FunctionName",
|
name: "IX_Sas_T_SqlFunction_SchemaName_FunctionName",
|
||||||
table: "Sqm_T_SqlFunction",
|
table: "Sas_T_SqlFunction",
|
||||||
columns: new[] { "SchemaName", "FunctionName" });
|
columns: new[] { "SchemaName", "FunctionName" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlFunction_Status",
|
name: "IX_Sas_T_SqlFunction_Status",
|
||||||
table: "Sqm_T_SqlFunction",
|
table: "Sas_T_SqlFunction",
|
||||||
column: "Status");
|
column: "Status");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlQuery_Category",
|
name: "IX_Sas_T_SqlQuery_Category",
|
||||||
table: "Sqm_T_SqlQuery",
|
table: "Sas_T_SqlQuery",
|
||||||
column: "Category");
|
column: "Category");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlQuery_Code",
|
name: "IX_Sas_T_SqlQuery_Code",
|
||||||
table: "Sqm_T_SqlQuery",
|
table: "Sas_T_SqlQuery",
|
||||||
column: "Code");
|
column: "Code");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlQuery_DataSourceCode",
|
name: "IX_Sas_T_SqlQuery_DataSourceCode",
|
||||||
table: "Sqm_T_SqlQuery",
|
table: "Sas_T_SqlQuery",
|
||||||
column: "DataSourceCode");
|
column: "DataSourceCode");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlQuery_Status",
|
name: "IX_Sas_T_SqlQuery_Status",
|
||||||
table: "Sqm_T_SqlQuery",
|
table: "Sas_T_SqlQuery",
|
||||||
column: "Status");
|
column: "Status");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlStoredProcedure_DataSourceCode",
|
name: "IX_Sas_T_SqlStoredProcedure_DataSourceCode",
|
||||||
table: "Sqm_T_SqlStoredProcedure",
|
table: "Sas_T_SqlStoredProcedure",
|
||||||
column: "DataSourceCode");
|
column: "DataSourceCode");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlStoredProcedure_IsDeployed",
|
name: "IX_Sas_T_SqlStoredProcedure_IsDeployed",
|
||||||
table: "Sqm_T_SqlStoredProcedure",
|
table: "Sas_T_SqlStoredProcedure",
|
||||||
column: "IsDeployed");
|
column: "IsDeployed");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlStoredProcedure_SchemaName_ProcedureName",
|
name: "IX_Sas_T_SqlStoredProcedure_SchemaName_ProcedureName",
|
||||||
table: "Sqm_T_SqlStoredProcedure",
|
table: "Sas_T_SqlStoredProcedure",
|
||||||
columns: new[] { "SchemaName", "ProcedureName" });
|
columns: new[] { "SchemaName", "ProcedureName" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlStoredProcedure_Status",
|
name: "IX_Sas_T_SqlStoredProcedure_Status",
|
||||||
table: "Sqm_T_SqlStoredProcedure",
|
table: "Sas_T_SqlStoredProcedure",
|
||||||
column: "Status");
|
column: "Status");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlView_DataSourceCode",
|
name: "IX_Sas_T_SqlTableField_EntityId",
|
||||||
table: "Sqm_T_SqlView",
|
table: "Sas_T_SqlTableField",
|
||||||
|
column: "EntityId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Sas_T_SqlView_DataSourceCode",
|
||||||
|
table: "Sas_T_SqlView",
|
||||||
column: "DataSourceCode");
|
column: "DataSourceCode");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlView_IsDeployed",
|
name: "IX_Sas_T_SqlView_IsDeployed",
|
||||||
table: "Sqm_T_SqlView",
|
table: "Sas_T_SqlView",
|
||||||
column: "IsDeployed");
|
column: "IsDeployed");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlView_SchemaName_ViewName",
|
name: "IX_Sas_T_SqlView_SchemaName_ViewName",
|
||||||
table: "Sqm_T_SqlView",
|
table: "Sas_T_SqlView",
|
||||||
columns: new[] { "SchemaName", "ViewName" });
|
columns: new[] { "SchemaName", "ViewName" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sqm_T_SqlView_Status",
|
name: "IX_Sas_T_SqlView_Status",
|
||||||
table: "Sqm_T_SqlView",
|
table: "Sas_T_SqlView",
|
||||||
column: "Status");
|
column: "Status");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3426,18 +3390,12 @@ namespace Sozsoft.Platform.Migrations
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_T_CrudEndpoint");
|
name: "Sas_T_CrudEndpoint");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Sas_T_CrudMigration");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_T_CustomComponent");
|
name: "Sas_T_CustomComponent");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_T_CustomEndpoint");
|
name: "Sas_T_CustomEndpoint");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Sas_T_CustomEntityField");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_T_DynamicService");
|
name: "Sas_T_DynamicService");
|
||||||
|
|
||||||
|
|
@ -3457,16 +3415,19 @@ namespace Sozsoft.Platform.Migrations
|
||||||
name: "Sas_T_Sector");
|
name: "Sas_T_Sector");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sqm_T_SqlFunction");
|
name: "Sas_T_SqlFunction");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sqm_T_SqlQuery");
|
name: "Sas_T_SqlQuery");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sqm_T_SqlStoredProcedure");
|
name: "Sas_T_SqlStoredProcedure");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sqm_T_SqlView");
|
name: "Sas_T_SqlTableField");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Sas_T_SqlView");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "AbpEntityChanges");
|
name: "AbpEntityChanges");
|
||||||
|
|
@ -3523,10 +3484,10 @@ namespace Sozsoft.Platform.Migrations
|
||||||
name: "Sas_T_Branch");
|
name: "Sas_T_Branch");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_T_CustomEntity");
|
name: "Sas_T_ReportCategory");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_T_ReportCategory");
|
name: "Sas_T_SqlTable");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "AbpAuditLogs");
|
name: "AbpAuditLogs");
|
||||||
|
|
@ -1416,67 +1416,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_T_CrudEndpoint", (string)null);
|
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 =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Currency", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
|
|
@ -1685,171 +1624,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_T_CustomEndpoint", (string)null);
|
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 =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.DataSource", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -3998,6 +3772,163 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Adm_T_SkillType", (string)null);
|
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 =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Uom", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
|
|
@ -4660,7 +4591,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
b.HasIndex("SchemaName", "FunctionName");
|
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 =>
|
modelBuilder.Entity("Sozsoft.SqlQueryManager.Domain.Entities.SqlQuery", b =>
|
||||||
|
|
@ -4760,7 +4691,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
b.HasIndex("Status");
|
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 =>
|
modelBuilder.Entity("Sozsoft.SqlQueryManager.Domain.Entities.SqlStoredProcedure", b =>
|
||||||
|
|
@ -4858,7 +4789,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
b.HasIndex("SchemaName", "ProcedureName");
|
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 =>
|
modelBuilder.Entity("Sozsoft.SqlQueryManager.Domain.Entities.SqlView", b =>
|
||||||
|
|
@ -4955,7 +4886,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
b.HasIndex("SchemaName", "ViewName");
|
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 =>
|
modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b =>
|
||||||
|
|
@ -6876,7 +6807,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CrudEndpoint", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.CrudEndpoint", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Sozsoft.Platform.Entities.CustomEntity", "Entity")
|
b.HasOne("Sozsoft.Platform.Entities.SqlTable", "Entity")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("EntityId")
|
.HasForeignKey("EntityId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
|
@ -6885,28 +6816,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Navigation("Entity");
|
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 =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.District", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Sozsoft.Platform.Entities.City", null)
|
b.HasOne("Sozsoft.Platform.Entities.City", null)
|
||||||
|
|
@ -6997,6 +6906,17 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Navigation("SkillType");
|
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 =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Uom", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Sozsoft.Platform.Entities.UomCategory", "UomCategory")
|
b.HasOne("Sozsoft.Platform.Entities.UomCategory", "UomCategory")
|
||||||
|
|
@ -7219,11 +7139,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Navigation("Cities");
|
b.Navigation("Cities");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.CustomEntity", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Fields");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Items");
|
b.Navigation("Items");
|
||||||
|
|
@ -7241,6 +7156,11 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Navigation("Skills");
|
b.Navigation("Skills");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sozsoft.Platform.Entities.SqlTable", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Fields");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.UomCategory", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.UomCategory", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Uoms");
|
b.Navigation("Uoms");
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,8 @@
|
||||||
"dependencies": []
|
"dependencies": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ProductListComponent",
|
"name": "RoleListComponent",
|
||||||
"code": "const ProductListComponent = ({\n title = \"Product\"\n}) => {\n return (\n <DynamicEntityComponent id=\"c_mdljvvmq_fno52v\" title={title} />\n );\n};\n\nexport default ProductListComponent;",
|
"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,
|
"props": null,
|
||||||
"description": null,
|
"description": null,
|
||||||
"isActive": true,
|
"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 navigate = useNavigate()
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{
|
|
||||||
id: 'dashboard',
|
|
||||||
label: translate('::App.DeveloperKit.Dashboard'),
|
|
||||||
icon: FaTachometerAlt,
|
|
||||||
path: ROUTES_ENUM.protected.saas.developerKit.dashboard,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'entities',
|
id: 'entities',
|
||||||
label: translate('::App.DeveloperKit.Entity'),
|
label: translate('::App.DeveloperKit.Entity'),
|
||||||
icon: FaDatabase,
|
icon: FaDatabase,
|
||||||
path: ROUTES_ENUM.protected.saas.developerKit.entities,
|
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',
|
id: 'endpoints',
|
||||||
label: translate('::App.DeveloperKit.CrudEndpoints'),
|
label: translate('::App.DeveloperKit.CrudEndpoints'),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import React, { createContext, useContext, useState, useEffect } from "react";
|
import React, { createContext, useContext, useState, useEffect } from "react";
|
||||||
|
import { developerKitService } from "@/services/developerKit.service";
|
||||||
import {
|
import {
|
||||||
developerKitService,
|
CrudEndpoint,
|
||||||
} from "@/services/developerKit.service";
|
CreateUpdateSqlTableDto,
|
||||||
import { CrudEndpoint, ApiMigration, CreateUpdateCustomEntityDto, CustomEntity } from "@/proxy/developerKit/models";
|
SqlTable,
|
||||||
|
} from "@/proxy/developerKit/models";
|
||||||
|
|
||||||
export const FIELD_TYPE_OPTIONS = [
|
export const FIELD_TYPE_OPTIONS = [
|
||||||
{ label: "String", value: "string" },
|
{ label: "String", value: "string" },
|
||||||
|
|
@ -16,23 +18,17 @@ export const FIELD_TYPE_OPTIONS = [
|
||||||
export type EntityFieldType = (typeof FIELD_TYPE_OPTIONS)[number]["value"];
|
export type EntityFieldType = (typeof FIELD_TYPE_OPTIONS)[number]["value"];
|
||||||
|
|
||||||
interface EntityContextType {
|
interface EntityContextType {
|
||||||
entities: CustomEntity[];
|
entities: SqlTable[];
|
||||||
migrations: ApiMigration[];
|
|
||||||
generatedEndpoints: CrudEndpoint[];
|
generatedEndpoints: CrudEndpoint[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
// Entity operations
|
// Entity operations
|
||||||
addEntity: (entity: CreateUpdateCustomEntityDto) => Promise<void>;
|
addEntity: (entity: CreateUpdateSqlTableDto) => Promise<void>;
|
||||||
updateEntity: (id: string, entity: CreateUpdateCustomEntityDto) => Promise<void>;
|
updateEntity: (id: string, entity: CreateUpdateSqlTableDto) => Promise<void>;
|
||||||
deleteEntity: (id: string) => Promise<void>;
|
deleteEntity: (id: string) => Promise<void>;
|
||||||
getEntity: (id: string) => CustomEntity | undefined;
|
getEntity: (id: string) => SqlTable | undefined;
|
||||||
refreshEntities: () => Promise<void>;
|
refreshEntities: () => Promise<void>;
|
||||||
toggleEntityActiveStatus: (id: string) => 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
|
// Generated endpoint operations
|
||||||
generateCrudEndpoints: (entityId: string) => Promise<void>;
|
generateCrudEndpoints: (entityId: string) => Promise<void>;
|
||||||
toggleEndpoint: (endpointId: string) => Promise<void>;
|
toggleEndpoint: (endpointId: string) => Promise<void>;
|
||||||
|
|
@ -51,28 +47,21 @@ export const useEntities = () => {
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
children,
|
const [entities, setEntities] = useState<SqlTable[]>([]);
|
||||||
}) => {
|
const [generatedEndpoints, setGeneratedEndpoints] = useState<CrudEndpoint[]>([]);
|
||||||
const [entities, setEntities] = useState<CustomEntity[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const [migrations, setMigrations] = useState<ApiMigration[]>([]);
|
|
||||||
const [generatedEndpoints, setGeneratedEndpoints] = useState<CrudEndpoint[]>([]);
|
|
||||||
|
|
||||||
const refreshEntities = async () => {
|
const refreshEntities = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const [entitiesData, migrationsData, generatedEndpointsData] =
|
const [entitiesData, generatedEndpointsData] = await Promise.all([
|
||||||
await Promise.all([
|
developerKitService.getSqlTables(),
|
||||||
developerKitService.getCustomEntities(),
|
developerKitService.getGeneratedEndpoints(),
|
||||||
developerKitService.getMigrations(),
|
]);
|
||||||
developerKitService.getGeneratedEndpoints(),
|
|
||||||
]);
|
|
||||||
setEntities(entitiesData.items || []);
|
setEntities(entitiesData.items || []);
|
||||||
setMigrations(migrationsData.items || []);
|
|
||||||
setGeneratedEndpoints(generatedEndpointsData.items || []);
|
setGeneratedEndpoints(generatedEndpointsData.items || []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to fetch data");
|
setError(err instanceof Error ? err.message : "Failed to fetch data");
|
||||||
|
|
@ -86,11 +75,11 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
refreshEntities();
|
refreshEntities();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const addEntity = async (entityData: CreateUpdateCustomEntityDto) => {
|
const addEntity = async (entityData: CreateUpdateSqlTableDto) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const newEntity = await developerKitService.createCustomEntity(entityData);
|
const newEntity = await developerKitService.createSqlTable(entityData);
|
||||||
setEntities((prev) => [...prev, newEntity]);
|
setEntities((prev) => [...prev, newEntity]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to create entity");
|
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 (
|
const updateEntity = async (id: string, entityData: CreateUpdateSqlTableDto) => {
|
||||||
id: string,
|
|
||||||
entityData: CreateUpdateCustomEntityDto
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const updatedEntity = await developerKitService.updateCustomEntity(id, entityData);
|
const updatedEntity = await developerKitService.updateSqlTable(id, entityData);
|
||||||
|
setEntities((prev) => prev.map((e) => (e.id === id ? updatedEntity : e)));
|
||||||
// State'i güncelle
|
|
||||||
setEntities((prev) =>
|
|
||||||
prev.map((entity) => (entity.id === id ? updatedEntity : entity))
|
|
||||||
);
|
|
||||||
|
|
||||||
// Güvenlik için tüm varlıkları yenile
|
|
||||||
await refreshEntities();
|
await refreshEntities();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to update entity");
|
setError(err instanceof Error ? err.message : "Failed to update entity");
|
||||||
|
|
@ -128,16 +108,9 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
await developerKitService.deleteCustomEntity(id);
|
await developerKitService.deleteSqlTable(id);
|
||||||
|
setEntities((prev) => prev.filter((e) => e.id !== id));
|
||||||
// Remove entity from state
|
setGeneratedEndpoints((prev) => prev.filter((ep) => ep.entityId !== id));
|
||||||
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));
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to delete entity");
|
setError(err instanceof Error ? err.message : "Failed to delete entity");
|
||||||
throw err;
|
throw err;
|
||||||
|
|
@ -150,88 +123,28 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
await developerKitService.toggleCustomEntityActiveStatus(id);
|
await developerKitService.toggleSqlTableActiveStatus(id);
|
||||||
// Refresh entities to get updated status
|
|
||||||
await refreshEntities();
|
await refreshEntities();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(
|
setError(err instanceof Error ? err.message : "Failed to toggle entity status");
|
||||||
err instanceof Error ? err.message : "Failed to toggle entity status"
|
|
||||||
);
|
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEntity = (id: string): CustomEntity | undefined => {
|
const getEntity = (id: string): SqlTable | undefined =>
|
||||||
return entities.find((entity) => entity.id === id);
|
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) => {
|
const generateCrudEndpoints = async (entityId: string) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const endpointsData = await developerKitService.generateCrudEndpoints(entityId);
|
const endpointsData = await developerKitService.generateCrudEndpoints(entityId);
|
||||||
// Replace existing endpoints for this entity
|
|
||||||
setGeneratedEndpoints((prev) => [
|
setGeneratedEndpoints((prev) => [
|
||||||
...prev.filter((e) => e.entityId !== entityId),
|
...prev.filter((e) => e.entityId !== entityId),
|
||||||
...endpointsData.items || [],
|
...(endpointsData.items || []),
|
||||||
]);
|
]);
|
||||||
// Refresh entities to get updated endpoint status
|
|
||||||
await refreshEntities();
|
await refreshEntities();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to generate CRUD endpoints");
|
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);
|
setError(null);
|
||||||
const updatedEndpoint = await developerKitService.toggleGeneratedEndpoint(endpointId);
|
const updatedEndpoint = await developerKitService.toggleGeneratedEndpoint(endpointId);
|
||||||
setGeneratedEndpoints((prev) =>
|
setGeneratedEndpoints((prev) =>
|
||||||
prev.map((e) => (e.id === endpointId ? updatedEndpoint : e))
|
prev.map((e) => (e.id === endpointId ? updatedEndpoint : e)),
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to toggle endpoint");
|
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[] => {
|
const getEntityEndpoints = (entityId: string): CrudEndpoint[] =>
|
||||||
return generatedEndpoints.filter((endpoint) => endpoint.entityId === entityId);
|
generatedEndpoints.filter((e) => e.entityId === entityId);
|
||||||
};
|
|
||||||
|
|
||||||
const deleteGeneratedEndpoint = async (id: string) => {
|
const deleteGeneratedEndpoint = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
await developerKitService.deleteGeneratedEndpoint(id);
|
await developerKitService.deleteGeneratedEndpoint(id);
|
||||||
setGeneratedEndpoints((prev) => prev.filter((endpoint) => endpoint.id !== id));
|
setGeneratedEndpoints((prev) => prev.filter((e) => e.id !== id));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to delete generated endpoint");
|
setError(err instanceof Error ? err.message : "Failed to delete generated endpoint");
|
||||||
throw err;
|
throw err;
|
||||||
|
|
@ -279,7 +191,6 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
<EntityContext.Provider
|
<EntityContext.Provider
|
||||||
value={{
|
value={{
|
||||||
entities,
|
entities,
|
||||||
migrations,
|
|
||||||
generatedEndpoints,
|
generatedEndpoints,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
|
|
@ -289,10 +200,6 @@ export const EntityProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
getEntity,
|
getEntity,
|
||||||
refreshEntities,
|
refreshEntities,
|
||||||
toggleEntityActiveStatus,
|
toggleEntityActiveStatus,
|
||||||
generateMigration,
|
|
||||||
applyMigration,
|
|
||||||
getEntityMigrations,
|
|
||||||
deleteMigration,
|
|
||||||
generateCrudEndpoints,
|
generateCrudEndpoints,
|
||||||
toggleEndpoint,
|
toggleEndpoint,
|
||||||
getEntityEndpoints,
|
getEntityEndpoints,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export interface CustomEntity {
|
export interface SqlTable {
|
||||||
id: string;
|
id: string;
|
||||||
menu: string;
|
menu: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -8,15 +8,13 @@ export interface CustomEntity {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
isFullAuditedEntity: boolean;
|
isFullAuditedEntity: boolean;
|
||||||
isMultiTenant: boolean;
|
isMultiTenant: boolean;
|
||||||
migrationStatus: "pending" | "applied" | "failed";
|
|
||||||
endpointStatus: "pending" | "applied" | "failed";
|
endpointStatus: "pending" | "applied" | "failed";
|
||||||
migrationId?: string;
|
fields: SqlTableField[];
|
||||||
fields: CustomEntityField[];
|
|
||||||
creationTime: string;
|
creationTime: string;
|
||||||
lastModificationTime?: string;
|
lastModificationTime?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomEntityField {
|
export interface SqlTableField {
|
||||||
id: string;
|
id: string;
|
||||||
entityId: string;
|
entityId: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -31,7 +29,7 @@ export interface CustomEntityField {
|
||||||
displayOrder: number;
|
displayOrder: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateUpdateCustomEntityFieldDto {
|
export interface CreateUpdateSqlTableFieldDto {
|
||||||
id?: string;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: "string" | "number" | "boolean" | "date" | "guid" | "decimal";
|
type: "string" | "number" | "boolean" | "date" | "guid" | "decimal";
|
||||||
|
|
@ -43,7 +41,7 @@ export interface CreateUpdateCustomEntityFieldDto {
|
||||||
displayOrder: number;
|
displayOrder: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateUpdateCustomEntityDto {
|
export interface CreateUpdateSqlTableDto {
|
||||||
menu: string;
|
menu: string;
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
|
@ -52,7 +50,7 @@ export interface CreateUpdateCustomEntityDto {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
isFullAuditedEntity: boolean;
|
isFullAuditedEntity: boolean;
|
||||||
isMultiTenant: boolean;
|
isMultiTenant: boolean;
|
||||||
fields: CreateUpdateCustomEntityFieldDto[];
|
fields: CreateUpdateSqlTableFieldDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CrudEndpoint {
|
export interface CrudEndpoint {
|
||||||
|
|
@ -110,25 +108,3 @@ export interface CreateUpdateCustomComponentDto {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
dependencies?: string;
|
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',
|
entities: '/admin/developerkit/entities',
|
||||||
entitiesNew: '/admin/developerkit/entities/new',
|
entitiesNew: '/admin/developerkit/entities/new',
|
||||||
entitiesEdit: '/admin/developerkit/entities/edit/:id',
|
entitiesEdit: '/admin/developerkit/entities/edit/:id',
|
||||||
migrations: '/admin/developerkit/migrations',
|
|
||||||
endpoints: '/admin/developerkit/endpoints',
|
endpoints: '/admin/developerkit/endpoints',
|
||||||
endpointsNew: '/admin/developerkit/endpoints/new',
|
endpointsNew: '/admin/developerkit/endpoints/new',
|
||||||
components: '/admin/developerkit/components',
|
components: '/admin/developerkit/components',
|
||||||
|
|
|
||||||
|
|
@ -2,69 +2,67 @@ import { PagedResultDto } from '@/proxy'
|
||||||
import apiService from './api.service'
|
import apiService from './api.service'
|
||||||
import {
|
import {
|
||||||
CrudEndpoint,
|
CrudEndpoint,
|
||||||
ApiMigration,
|
|
||||||
CreateUpdateCrudEndpointDto,
|
CreateUpdateCrudEndpointDto,
|
||||||
CreateUpdateApiMigrationDto,
|
|
||||||
CreateUpdateCustomComponentDto,
|
CreateUpdateCustomComponentDto,
|
||||||
CreateUpdateCustomEntityDto,
|
CreateUpdateSqlTableDto,
|
||||||
CustomComponent,
|
CustomComponent,
|
||||||
CustomComponentDto,
|
CustomComponentDto,
|
||||||
CustomEntity,
|
SqlTable,
|
||||||
} from '@/proxy/developerKit/models'
|
} from '@/proxy/developerKit/models'
|
||||||
|
|
||||||
class DeveloperKitService {
|
class DeveloperKitService {
|
||||||
async getCustomEntities(): Promise<PagedResultDto<CustomEntity>> {
|
async getSqlTables(): Promise<PagedResultDto<SqlTable>> {
|
||||||
const response = await apiService.fetchData<PagedResultDto<CustomEntity>>({
|
const response = await apiService.fetchData<PagedResultDto<SqlTable>>({
|
||||||
url: '/api/app/custom-entity',
|
url: '/api/app/sql-table',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async getActiveCustomEntities(): Promise<PagedResultDto<CustomEntity>> {
|
async getActiveSqlTables(): Promise<PagedResultDto<SqlTable>> {
|
||||||
const response = await apiService.fetchData<PagedResultDto<CustomEntity>>({
|
const response = await apiService.fetchData<PagedResultDto<SqlTable>>({
|
||||||
url: '/api/app/custom-entity/active-entities',
|
url: '/api/app/sql-table/active-tables',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCustomEntity(id: string): Promise<CustomEntity> {
|
async getSqlTable(id: string): Promise<SqlTable> {
|
||||||
const response = await apiService.fetchData<CustomEntity>({
|
const response = await apiService.fetchData<SqlTable>({
|
||||||
url: `/api/app/custom-entity/${id}`,
|
url: `/api/app/sql-table/${id}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async createCustomEntity(data: CreateUpdateCustomEntityDto): Promise<CustomEntity> {
|
async createSqlTable(data: CreateUpdateSqlTableDto): Promise<SqlTable> {
|
||||||
const response = await apiService.fetchData<CustomEntity>({
|
const response = await apiService.fetchData<SqlTable>({
|
||||||
url: '/api/app/custom-entity',
|
url: '/api/app/sql-table',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: data as any,
|
data: data as any,
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCustomEntity(id: string, entity: CreateUpdateCustomEntityDto): Promise<CustomEntity> {
|
async updateSqlTable(id: string, entity: CreateUpdateSqlTableDto): Promise<SqlTable> {
|
||||||
const response = await apiService.fetchData<CustomEntity>({
|
const response = await apiService.fetchData<SqlTable>({
|
||||||
url: `/api/app/custom-entity/${id}`,
|
url: `/api/app/sql-table/${id}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: entity as any,
|
data: entity as any,
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteCustomEntity(id: string): Promise<void> {
|
async deleteSqlTable(id: string): Promise<void> {
|
||||||
await apiService.fetchData<void>({
|
await apiService.fetchData<void>({
|
||||||
url: `/api/app/custom-entity/${id}`,
|
url: `/api/app/sql-table/${id}`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggleCustomEntityActiveStatus(id: string): Promise<void> {
|
async toggleSqlTableActiveStatus(id: string): Promise<void> {
|
||||||
await apiService.fetchData<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',
|
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
|
// Generated Endpoint endpoints
|
||||||
async getGeneratedEndpoints(): Promise<PagedResultDto<CrudEndpoint>> {
|
async getGeneratedEndpoints(): Promise<PagedResultDto<CrudEndpoint>> {
|
||||||
const response = await apiService.fetchData<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}
|
defaultTitle={APP_NAME}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mb-8">
|
<div className="mb-6 mt-2">
|
||||||
<Steps current={currentStep}>
|
<Steps current={currentStep}>
|
||||||
<Steps.Item title={translate('::ListForms.Wizard.MenuInfo') || 'Menu Info'} />
|
<Steps.Item title={translate('::ListForms.Wizard.MenuInfo') || 'Menu Info'} />
|
||||||
<Steps.Item
|
<Steps.Item
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
toast,
|
toast,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@/components/ui'
|
} 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 { ListFormJsonRowDto } from '@/proxy/admin/list-form/models'
|
||||||
import { SelectBoxOption } from '@/types/shared'
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
import { useStoreActions, useStoreState } from '@/store'
|
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 DeveloperLayout from '@/components/layouts/DeveloperLayout'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
const ComponentDetailPage: React.FC = () => {
|
const ComponentEditorPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<DeveloperLayout>
|
<DeveloperLayout>
|
||||||
<ComponentEditor />
|
<ComponentEditor />
|
||||||
|
|
@ -10,4 +10,4 @@ const ComponentDetailPage: React.FC = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ComponentDetailPage
|
export default ComponentEditorPage
|
||||||
|
|
@ -16,7 +16,9 @@ import {
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
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 ComponentManager: React.FC = () => {
|
||||||
const { components, loading, updateComponent, deleteComponent } = useComponents()
|
const { components, loading, updateComponent, deleteComponent } = useComponents()
|
||||||
|
|
@ -89,24 +91,13 @@ const ComponentManager: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<Helmet
|
||||||
<div>
|
titleTemplate={`%s | ${APP_NAME}`}
|
||||||
<h1 className="text-2xl font-bold text-slate-900">
|
title={translate('::' + 'App.DeveloperKit.Components')}
|
||||||
{translate('::App.DeveloperKit.Components')}
|
defaultTitle={APP_NAME}
|
||||||
</h1>
|
></Helmet>
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Statistics Cards */}
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-2 mb-4">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-4">
|
|
||||||
{stats.map((stat, index) => (
|
{stats.map((stat, index) => (
|
||||||
<div key={index} className="bg-white rounded-lg border border-slate-200 p-6">
|
<div key={index} className="bg-white rounded-lg border border-slate-200 p-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
|
|
@ -150,6 +141,15 @@ const ComponentManager: React.FC = () => {
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Components List */}
|
{/* 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 DeveloperLayout from '@/components/layouts/DeveloperLayout'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
const ComponentPage: React.FC = () => {
|
const ComponentManagerPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<DeveloperLayout>
|
<DeveloperLayout>
|
||||||
<ComponentManager />
|
<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,
|
postTestCompile,
|
||||||
TestCompileDto,
|
TestCompileDto,
|
||||||
} from '@/services/dynamicService.service'
|
} from '@/services/dynamicService.service'
|
||||||
|
import { Helmet } from 'react-helmet'
|
||||||
|
import { APP_NAME } from '@/constants/app.constant'
|
||||||
|
|
||||||
const DynamicAppServiceEditor: React.FC = () => {
|
const DynamicAppServiceEditor: React.FC = () => {
|
||||||
// State
|
// State
|
||||||
|
|
@ -254,6 +256,12 @@ namespace DynamicServices
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
<Helmet
|
||||||
|
titleTemplate={`%s | ${APP_NAME}`}
|
||||||
|
title={translate('::' + 'App.DeveloperKit.DynamicServices')}
|
||||||
|
defaultTitle={APP_NAME}
|
||||||
|
></Helmet>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div>
|
<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'
|
} from '@/proxy/sql-query-manager/models'
|
||||||
import { SqlObjectType } from '@/proxy/sql-query-manager/models'
|
import { SqlObjectType } from '@/proxy/sql-query-manager/models'
|
||||||
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
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 { FaCheckCircle } from 'react-icons/fa'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import SqlObjectExplorer from './components/SqlObjectExplorer'
|
import SqlObjectExplorer from './SqlObjectExplorer'
|
||||||
import SqlEditor, { SqlEditorRef } from './components/SqlEditor'
|
import SqlEditor, { SqlEditorRef } from './SqlEditor'
|
||||||
import SqlResultsGrid from './components/SqlResultsGrid'
|
import SqlResultsGrid from './SqlResultsGrid'
|
||||||
import SqlObjectProperties from './components/SqlObjectProperties'
|
import SqlObjectProperties from './SqlObjectProperties'
|
||||||
import TemplateDialog from './components/TemplateDialog'
|
import SqlTableDesignerDialog from './SqlTableDesignerDialog'
|
||||||
import TableDesignerDialog from './components/TableDesignerDialog'
|
|
||||||
import { Splitter } from '@/components/codeLayout/Splitter'
|
import { Splitter } from '@/components/codeLayout/Splitter'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
|
|
@ -72,18 +71,12 @@ const SqlQueryManager = () => {
|
||||||
detectedName: '',
|
detectedName: '',
|
||||||
isExistingObject: false,
|
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 [showTemplateConfirmDialog, setShowTemplateConfirmDialog] = useState(false)
|
||||||
const [pendingTemplate, setPendingTemplate] = useState<{ content: string; type: string } | null>(
|
const [pendingTemplate, setPendingTemplate] = useState<{ content: string; type: string } | null>(
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
|
const [showTableDesignerDialog, setShowTableDesignerDialog] = useState(false)
|
||||||
const handleDesignTable = (schemaName: string, tableName: string) => {
|
const [designTableData, setDesignTableData] = useState<{ schemaName: string; tableName: string } | null>(null)
|
||||||
setDesignTableData({ schemaName, tableName })
|
|
||||||
setShowTableDesignerDialog(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadDataSources()
|
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(
|
const handleObjectSelect = useCallback(
|
||||||
async (object: SqlObject | null, objectType: SqlObjectType | null) => {
|
async (object: SqlObject | null, objectType: SqlObjectType | null) => {
|
||||||
if (state.isDirty) {
|
if (state.isDirty) {
|
||||||
|
|
@ -878,12 +881,9 @@ GO`,
|
||||||
selectedObject={state.selectedObject}
|
selectedObject={state.selectedObject}
|
||||||
onTemplateSelect={handleTemplateSelect}
|
onTemplateSelect={handleTemplateSelect}
|
||||||
onShowTableColumns={handleShowTableColumns}
|
onShowTableColumns={handleShowTableColumns}
|
||||||
onDesignTable={handleDesignTable}
|
|
||||||
onNewTable={() => {
|
|
||||||
setDesignTableData(null)
|
|
||||||
setShowTableDesignerDialog(true)
|
|
||||||
}}
|
|
||||||
refreshTrigger={state.refreshTrigger}
|
refreshTrigger={state.refreshTrigger}
|
||||||
|
onNewTable={handleNewTable}
|
||||||
|
onDesignTable={handleDesignTable}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</AdaptableCard>
|
</AdaptableCard>
|
||||||
|
|
@ -975,27 +975,6 @@ GO`,
|
||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* Template Confirmation Dialog */}
|
||||||
<Dialog
|
<Dialog
|
||||||
isOpen={showTemplateConfirmDialog}
|
isOpen={showTemplateConfirmDialog}
|
||||||
|
|
@ -1016,6 +995,20 @@ GO`,
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</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 */}
|
{/* Save Dialog */}
|
||||||
<Dialog
|
<Dialog
|
||||||
isOpen={showSaveDialog}
|
isOpen={showSaveDialog}
|
||||||
|
|
@ -9,19 +9,15 @@ import {
|
||||||
FaCloudUploadAlt,
|
FaCloudUploadAlt,
|
||||||
FaCheck,
|
FaCheck,
|
||||||
FaChevronRight,
|
FaChevronRight,
|
||||||
FaCog,
|
|
||||||
FaDatabase,
|
|
||||||
FaLink,
|
FaLink,
|
||||||
FaEdit,
|
FaEdit,
|
||||||
FaTimes,
|
FaTimes,
|
||||||
FaArrowRight,
|
FaArrowRight,
|
||||||
FaInfoCircle,
|
|
||||||
FaExclamationTriangle,
|
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
||||||
import { MenuService } from '@/services/menu.service'
|
import { MenuService } from '@/services/menu.service'
|
||||||
import { developerKitService } from '@/services/developerKit.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'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -482,10 +478,10 @@ function generateAlterTableSql(
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map TableDesigner data-type → CustomEntityField type
|
// Map TableDesigner data-type → SqlTableField type
|
||||||
function colTypeToEntityFieldType(
|
function colTypeToEntityFieldType(
|
||||||
dt: SqlDataType,
|
dt: SqlDataType,
|
||||||
): CreateUpdateCustomEntityFieldDto['type'] {
|
): CreateUpdateSqlTableFieldDto['type'] {
|
||||||
if (dt === 'int' || dt === 'bigint') return 'number'
|
if (dt === 'int' || dt === 'bigint') return 'number'
|
||||||
if (dt === 'bit') return 'boolean'
|
if (dt === 'bit') return 'boolean'
|
||||||
if (dt === 'datetime2' || dt === 'date') return 'date'
|
if (dt === 'datetime2' || dt === 'date') return 'date'
|
||||||
|
|
@ -521,7 +517,7 @@ const DEFAULT_SETTINGS: TableSettings = {
|
||||||
|
|
||||||
// ─── Component ────────────────────────────────────────────────────────────────
|
// ─── Component ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const TableDesignerDialog = ({
|
const SqlTableDesignerDialog = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
|
@ -587,9 +583,9 @@ const TableDesignerDialog = ({
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => setColsLoading(false))
|
.finally(() => setColsLoading(false))
|
||||||
|
|
||||||
// Load CustomEntity metadata (menu, entityName, displayName, flags…)
|
// Load SqlTable metadata (menu, entityName, displayName, flags…)
|
||||||
developerKitService
|
developerKitService
|
||||||
.getCustomEntities()
|
.getSqlTables()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const match = (res.items ?? []).find(
|
const match = (res.items ?? []).find(
|
||||||
(e) => e.tableName.toLowerCase() === initialTableData.tableName.toLowerCase(),
|
(e) => e.tableName.toLowerCase() === initialTableData.tableName.toLowerCase(),
|
||||||
|
|
@ -808,19 +804,19 @@ const TableDesignerDialog = ({
|
||||||
|
|
||||||
// ── Deploy ─────────────────────────────────────────────────────────────────
|
// ── Deploy ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const syncCustomEntityMetadata = async (deployedTableName: string) => {
|
const syncSqlTableMetadata = async (deployedTableName: string) => {
|
||||||
try {
|
try {
|
||||||
const namedCols = columns.filter((c) => c.columnName.trim())
|
const namedCols = columns.filter((c) => c.columnName.trim())
|
||||||
|
|
||||||
// Find existing CustomEntity record by table name
|
// Find existing SqlTable record by table name
|
||||||
const listResult = await developerKitService.getCustomEntities()
|
const listResult = await developerKitService.getSqlTables()
|
||||||
const existing = (listResult.items ?? []).find(
|
const existing = (listResult.items ?? []).find(
|
||||||
(e) => e.tableName.toLowerCase() === deployedTableName.toLowerCase(),
|
(e) => e.tableName.toLowerCase() === deployedTableName.toLowerCase(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
// Update: keep existing metadata, sync fields (match by name to preserve IDs)
|
// 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(
|
const existingField = existing.fields?.find(
|
||||||
(f) => f.name.toLowerCase() === col.columnName.trim().toLowerCase(),
|
(f) => f.name.toLowerCase() === col.columnName.trim().toLowerCase(),
|
||||||
)
|
)
|
||||||
|
|
@ -837,7 +833,7 @@ const TableDesignerDialog = ({
|
||||||
displayOrder: i,
|
displayOrder: i,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const updateDto: CreateUpdateCustomEntityDto = {
|
const updateDto: CreateUpdateSqlTableDto = {
|
||||||
menu: existing.menu,
|
menu: existing.menu,
|
||||||
name: existing.name,
|
name: existing.name,
|
||||||
displayName: existing.displayName,
|
displayName: existing.displayName,
|
||||||
|
|
@ -848,10 +844,10 @@ const TableDesignerDialog = ({
|
||||||
isMultiTenant: existing.isMultiTenant,
|
isMultiTenant: existing.isMultiTenant,
|
||||||
fields: fieldDtos,
|
fields: fieldDtos,
|
||||||
}
|
}
|
||||||
await developerKitService.updateCustomEntity(existing.id, updateDto)
|
await developerKitService.updateSqlTable(existing.id, updateDto)
|
||||||
} else {
|
} else {
|
||||||
// Create new CustomEntity record
|
// Create new SqlTable record
|
||||||
const fieldDtos: CreateUpdateCustomEntityFieldDto[] = namedCols.map((col, i) => {
|
const fieldDtos: CreateUpdateSqlTableFieldDto[] = namedCols.map((col, i) => {
|
||||||
const maxLen = col.maxLength ? parseInt(col.maxLength, 10) || undefined : undefined
|
const maxLen = col.maxLength ? parseInt(col.maxLength, 10) || undefined : undefined
|
||||||
return {
|
return {
|
||||||
name: col.columnName.trim(),
|
name: col.columnName.trim(),
|
||||||
|
|
@ -864,7 +860,7 @@ const TableDesignerDialog = ({
|
||||||
displayOrder: i,
|
displayOrder: i,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const createDto: CreateUpdateCustomEntityDto = {
|
const createDto: CreateUpdateSqlTableDto = {
|
||||||
menu: settings.menuValue,
|
menu: settings.menuValue,
|
||||||
name: settings.entityName || deployedTableName,
|
name: settings.entityName || deployedTableName,
|
||||||
displayName: settings.displayName || deployedTableName,
|
displayName: settings.displayName || deployedTableName,
|
||||||
|
|
@ -875,7 +871,7 @@ const TableDesignerDialog = ({
|
||||||
isMultiTenant: settings.isMultiTenant,
|
isMultiTenant: settings.isMultiTenant,
|
||||||
fields: fieldDtos,
|
fields: fieldDtos,
|
||||||
}
|
}
|
||||||
await developerKitService.createCustomEntity(createDto)
|
await developerKitService.createSqlTable(createDto)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Silent — SQL deploy already succeeded; metadata sync failure is non-critical
|
// Silent — SQL deploy already succeeded; metadata sync failure is non-critical
|
||||||
|
|
@ -900,8 +896,8 @@ const TableDesignerDialog = ({
|
||||||
})
|
})
|
||||||
if (result.data.success) {
|
if (result.data.success) {
|
||||||
const deployedTable = settings.tableName || initialTableData?.tableName || ''
|
const deployedTable = settings.tableName || initialTableData?.tableName || ''
|
||||||
// Sync entity metadata to CustomEntity / CustomEntityField tables
|
// Sync entity metadata to SqlTable / SqlTableField tables
|
||||||
await syncCustomEntityMetadata(deployedTable)
|
await syncSqlTableMetadata(deployedTable)
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification type="success" title="Basarili">
|
<Notification type="success" title="Basarili">
|
||||||
Tablo basariyla {isEditMode ? 'güncellendi' : 'oluşturuldu'}: [dbo].[{deployedTable}]
|
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