diff --git a/api/src/Sozsoft.Platform.Application.Contracts/DeveloperKit/DynamicServiceDtos.cs b/api/src/Sozsoft.Platform.Application.Contracts/DeveloperKit/DynamicServiceDtos.cs index 7048bbe..c41dddc 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/DeveloperKit/DynamicServiceDtos.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/DeveloperKit/DynamicServiceDtos.cs @@ -127,6 +127,9 @@ public class PublishAppServiceRequestDto [StringLength(256)] [JsonPropertyName("primaryEntityType")] public string PrimaryEntityType { get; set; } + + [JsonPropertyName("isActive")] + public bool IsActive { get; set; } = true; } /// diff --git a/api/src/Sozsoft.Platform.Application/DeveloperKit/CrudEndpointGenerateAppService.cs b/api/src/Sozsoft.Platform.Application/DeveloperKit/CrudEndpointGenerateAppService.cs index 76851c5..95f8af9 100644 --- a/api/src/Sozsoft.Platform.Application/DeveloperKit/CrudEndpointGenerateAppService.cs +++ b/api/src/Sozsoft.Platform.Application/DeveloperKit/CrudEndpointGenerateAppService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Sozsoft.Platform.DeveloperKit; using Sozsoft.Platform.Entities; @@ -8,9 +7,11 @@ using Microsoft.EntityFrameworkCore; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; +using Microsoft.AspNetCore.Authorization; namespace Platform.Api.Application; +[Authorize] public class CrudEndpointGenerateAppService : CrudAppService< CrudEndpoint, CrudEndpointDto, diff --git a/api/src/Sozsoft.Platform.Application/DeveloperKit/CustomComponentAppService.cs b/api/src/Sozsoft.Platform.Application/DeveloperKit/CustomComponentAppService.cs index 8c477ac..d1e0cd9 100644 --- a/api/src/Sozsoft.Platform.Application/DeveloperKit/CustomComponentAppService.cs +++ b/api/src/Sozsoft.Platform.Application/DeveloperKit/CustomComponentAppService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Sozsoft.Platform.DeveloperKit; using Sozsoft.Platform.Entities; using Volo.Abp.Application.Dtos; @@ -9,6 +10,7 @@ using Volo.Abp.Domain.Repositories; namespace Platform.Api.Application; +[Authorize] public class CustomComponentAppService : CrudAppService< CustomComponent, CustomComponentDto, diff --git a/api/src/Sozsoft.Platform.Application/DeveloperKit/DynamicServiceAppService.cs b/api/src/Sozsoft.Platform.Application/DeveloperKit/DynamicServiceAppService.cs index 7f81ead..efef939 100644 --- a/api/src/Sozsoft.Platform.Application/DeveloperKit/DynamicServiceAppService.cs +++ b/api/src/Sozsoft.Platform.Application/DeveloperKit/DynamicServiceAppService.cs @@ -70,6 +70,7 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp existingService.DisplayName = request.DisplayName; existingService.Description = request.Description; existingService.PrimaryEntityType = request.PrimaryEntityType; + existingService.IsActive = request.IsActive; appService = await _dynamicAppServiceRepository.UpdateAsync(existingService); } @@ -84,13 +85,34 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp DisplayName = request.DisplayName, Description = request.Description, PrimaryEntityType = request.PrimaryEntityType, - ControllerName = GenerateControllerName(request.Name) + ControllerName = GenerateControllerName(request.Name), + IsActive = request.IsActive }; appService = await _dynamicAppServiceRepository.InsertAsync(appService); } var assemblyName = $"{appService.Name}_{appService.Version}"; + + // Pasif olarak yayınlanıyorsa mevcut kaydı kaldır, assembly yükleme + if (!request.IsActive) + { + DynamicServiceCompiler.NotifyAssemblyUnregistration?.Invoke( + CurrentTenant.Id ?? Guid.Empty, + appService.Name); + + appService.MarkCompilationSuccess(); + await _dynamicAppServiceRepository.UpdateAsync(appService); + + return new PublishResultDto + { + Success = true, + AppServiceId = appService.Id, + ControllerName = appService.ControllerName, + GeneratedEndpoints = new List() + }; + } + var loadResult = await _compiler.CompileAndRegisterForTenantAsync( CurrentTenant.Id ?? Guid.Empty, request.Code, @@ -176,9 +198,11 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp public virtual async Task DeleteAsync(Guid id) { var appService = await _dynamicAppServiceRepository.GetAsync(id); - - // TODO: Runtime'dan assembly'yi kaldırma işlemi - // (AssemblyLoadContext.Unload() çağrısı) + + // Runtime'dan assembly ve Swagger endpoint'ini kaldır + DynamicServiceCompiler.NotifyAssemblyUnregistration?.Invoke( + CurrentTenant.Id ?? Guid.Empty, + appService.Name); await _dynamicAppServiceRepository.DeleteAsync(id); } @@ -189,6 +213,30 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp var appService = await _dynamicAppServiceRepository.GetAsync(id); appService.IsActive = isActive; await _dynamicAppServiceRepository.UpdateAsync(appService); + + if (!isActive) + { + // Pasif yapılınca Swagger/MVC'den endpoint'i kaldır + DynamicServiceCompiler.NotifyAssemblyUnregistration?.Invoke( + CurrentTenant.Id ?? Guid.Empty, + appService.Name); + } + else if (appService.CompilationStatus == CompilationStatus.Success) + { + // Aktif yapılınca yeniden derle ve yayınla + var assemblyName = $"{appService.Name}_{appService.Version}"; + var result = await _compiler.CompileAndRegisterForTenantAsync( + CurrentTenant.Id ?? Guid.Empty, + appService.Code, + assemblyName); + + if (!result.Success) + { + Logger.LogWarning( + "Servis aktif edildi ancak yeniden derleme başarısız. Ad: {Name}, Hata: {Error}", + appService.Name, result.ErrorMessage); + } + } } [Authorize(AppCodes.DeveloperKits.DynamicServices.Manage)] diff --git a/api/src/Sozsoft.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs b/api/src/Sozsoft.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs index f797acf..a28d7c2 100644 --- a/api/src/Sozsoft.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs +++ b/api/src/Sozsoft.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs @@ -28,6 +28,20 @@ public class DynamicServiceCompiler : ITransientDependency // Assembly kaydı için delegate public static Action? NotifyAssemblyRegistration { get; set; } + // Assembly silinme bildirimi için delegate + public static Action? NotifyAssemblyUnregistration { get; set; } + + /// + /// Belirtilen tenant ve assembly adı prefix'ine ait assembly'leri tenant cache'inden kaldırır. + /// + public static void UnregisterTenantAssemblyByPrefix(Guid tenantId, string assemblyNamePrefix) + { + if (_tenantAssemblies.TryGetValue(tenantId, out var assemblies)) + { + assemblies.RemoveAll(a => a.GetName().Name?.StartsWith(assemblyNamePrefix) == true); + } + } + // Güvenlik için yasaklı namespace'ler private static readonly string[] ForbiddenNamespaces = { "System.IO", diff --git a/api/src/Sozsoft.Platform.Application/DeveloperKit/SqlTableAppService.cs b/api/src/Sozsoft.Platform.Application/DeveloperKit/SqlTableAppService.cs index e202120..1d46125 100644 --- a/api/src/Sozsoft.Platform.Application/DeveloperKit/SqlTableAppService.cs +++ b/api/src/Sozsoft.Platform.Application/DeveloperKit/SqlTableAppService.cs @@ -9,9 +9,11 @@ using System; using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Authorization; namespace Platform.Api.Application; +[Authorize] public class SqlTableAppService : CrudAppService< SqlTable, SqlTableDto, @@ -59,7 +61,7 @@ public class SqlTableAppService : CrudAppService< .FirstOrDefaultAsync(x => x.Id == id); if (entity == null) - throw new EntityNotFoundException($"CustomEntity with id {id} not found"); + throw new EntityNotFoundException($"Sql Table with id {id} not found"); return ObjectMapper.Map(entity); } @@ -83,7 +85,7 @@ public class SqlTableAppService : CrudAppService< .FirstOrDefaultAsync(x => x.Id == id); if (entity == null) - throw new EntityNotFoundException($"CustomEntity with id {id} not found"); + throw new EntityNotFoundException($"Sql Table with id {id} not found"); entity.IsActive = !entity.IsActive; await _repository.UpdateAsync(entity, autoSave: true); diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json index 96016ae..fc455dc 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json @@ -297,6 +297,20 @@ { "key": "admin.developerkit.dynamic-services", "path": "/admin/developerkit/dynamic-services", + "componentPath": "@/views/developerKit/DynamicServiceManager", + "routeType": "protected", + "authority": ["App.DeveloperKit.DynamicServices"] + }, + { + "key": "admin.developerkit.dynamic-services.new", + "path": "/admin/developerkit/dynamic-services/new", + "componentPath": "@/views/developerKit/DynamicServiceEditor", + "routeType": "protected", + "authority": ["App.DeveloperKit.DynamicServices"] + }, + { + "key": "admin.developerkit.dynamic-services.edit", + "path": "/admin/developerkit/dynamic-services/edit/:id", "componentPath": "@/views/developerKit/DynamicServiceEditor", "routeType": "protected", "authority": ["App.DeveloperKit.DynamicServices"] diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs b/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs index 8361b44..f1cd51f 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs @@ -27,6 +27,8 @@ namespace Sozsoft.Platform.DynamicServices // Bekleyen assembly kayıt istekleri private static readonly Queue _pendingRegistrations = new(); + // Bekleyen assembly silme istekleri + private static readonly Queue _pendingUnregistrations = new(); private static readonly object _lock = new(); public DynamicAssemblyRegistrationService( @@ -60,6 +62,22 @@ namespace Sozsoft.Platform.DynamicServices } } + /// + /// Bir servis adına ait assembly'nin Swagger/MVC'den kaldırılması istemi. + /// + public static void RequestAssemblyUnregistration(Guid tenantId, string serviceName) + { + lock (_lock) + { + _pendingUnregistrations.Enqueue(new AssemblyUnregistrationRequest + { + TenantId = tenantId, + ServiceName = serviceName, + RequestTime = DateTime.UtcNow + }); + } + } + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await Task.Delay(3000, stoppingToken); @@ -132,20 +150,35 @@ namespace Sozsoft.Platform.DynamicServices private async Task ProcessPendingRegistrations() { - var requests = new List(); - + var registrations = new List(); + var unregistrations = new List(); + lock (_lock) { while (_pendingRegistrations.Count > 0) + registrations.Add(_pendingRegistrations.Dequeue()); + + while (_pendingUnregistrations.Count > 0) + unregistrations.Add(_pendingUnregistrations.Dequeue()); + } + + foreach (var request in unregistrations) + { + try { - requests.Add(_pendingRegistrations.Dequeue()); + UnregisterAssembly(request); + } + catch (Exception ex) + { + _logger.LogError(ex, "Assembly kaydı silme başarısız. Tenant: {TenantId}, Servis: {Service}", + request.TenantId, request.ServiceName); } } - if (requests.Count == 0) + if (registrations.Count == 0) return; - foreach (var request in requests) + foreach (var request in registrations) { try { @@ -153,12 +186,50 @@ namespace Sozsoft.Platform.DynamicServices } catch (Exception ex) { - _logger.LogError(ex, "Assembly kaydı başarısız. Tenant: {TenantId}, Assembly: {Assembly}", + _logger.LogError(ex, "Assembly kaydı başarısız. Tenant: {TenantId}, Assembly: {Assembly}", request.TenantId, request.AssemblyName); } } } + private void UnregisterAssembly(AssemblyUnregistrationRequest request) + { + var servicePrefix = $"{request.ServiceName}_"; + + // ApplicationParts'tan kaldır + var partsToRemove = _partManager.ApplicationParts + .OfType() + .Where(p => p.Name.StartsWith(servicePrefix)) + .ToList(); + + foreach (var part in partsToRemove) + { + _partManager.ApplicationParts.Remove(part); + _logger.LogInformation("ApplicationPart kaldırıldı: {Name}", part.Name); + } + + // ConventionalControllerSettings'tan kaldır + var settingsToRemove = _mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings + .Where(s => s.Assembly.GetName().Name?.StartsWith(servicePrefix) == true) + .ToList(); + + foreach (var setting in settingsToRemove) + _mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings.Remove(setting); + + // DynamicServiceTypeRegistry'den kaldır + DynamicServiceTypeRegistry.UnregisterByAssemblyNamePrefix(servicePrefix); + + // DynamicServiceCompiler tenant assembly cache'inden kaldır + DynamicServiceCompiler.UnregisterTenantAssemblyByPrefix(request.TenantId, servicePrefix); + + // MVC/Swagger'ı yenile + _changeProvider.NotifyChanges(); + + _logger.LogInformation( + "Servis assembly'si başarıyla kaldırıldı: {ServiceName} (Tenant: {TenantId})", + request.ServiceName, request.TenantId); + } + private async Task RegisterAssembly(AssemblyRegistrationRequest request) { var lastUnderscoreIndex = request.AssemblyName.LastIndexOf('_'); @@ -281,5 +352,12 @@ namespace Sozsoft.Platform.DynamicServices public string AssemblyName { get; set; } public DateTime RequestTime { get; set; } } + + private class AssemblyUnregistrationRequest + { + public Guid TenantId { get; set; } + public string ServiceName { get; set; } + public DateTime RequestTime { get; set; } + } } } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicControllerActivator.cs b/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicControllerActivator.cs index 4c79481..22f5fda 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicControllerActivator.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicControllerActivator.cs @@ -14,24 +14,36 @@ namespace Sozsoft.Platform.DynamicServices; /// public static class DynamicServiceTypeRegistry { - private static readonly ConcurrentBag _registeredTypes = new(); + private static readonly ConcurrentDictionary _registeredTypes = new(); public static void RegisterType(Type type) { - if (!_registeredTypes.Contains(type)) - { - _registeredTypes.Add(type); - } + _registeredTypes.TryAdd(type, 0); } public static bool IsRegistered(Type type) { - return _registeredTypes.Contains(type); + return _registeredTypes.ContainsKey(type); } public static IEnumerable GetAllTypes() { - return _registeredTypes.ToList(); + return _registeredTypes.Keys.ToList(); + } + + /// + /// Belirtilen assembly adı prefix'iyle başlayan tüm kayıtlı tipleri kaldırır. + /// + public static void UnregisterByAssemblyNamePrefix(string assemblyNamePrefix) + { + var toRemove = _registeredTypes.Keys + .Where(t => t.Assembly.GetName().Name?.StartsWith(assemblyNamePrefix) == true) + .ToList(); + + foreach (var type in toRemove) + { + _registeredTypes.TryRemove(type, out _); + } } } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs index 772588a..ad3f553 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs @@ -453,6 +453,12 @@ public class PlatformHttpApiHostModule : AbpModule DynamicAssemblyRegistrationService.RequestAssemblyRegistration(tenantId, assembly, assemblyName); }; + // Setup delegate for dynamic service unregistration + DynamicServiceCompiler.NotifyAssemblyUnregistration = (tenantId, serviceName) => + { + DynamicAssemblyRegistrationService.RequestAssemblyUnregistration(tenantId, serviceName); + }; + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Program.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Program.cs index 0cc80af..cfd0230 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Program.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Program.cs @@ -154,6 +154,7 @@ public class Program // Dynamic Assembly Registration Delegate Setup DynamicServiceCompiler.NotifyAssemblyRegistration = DynamicAssemblyRegistrationService.RequestAssemblyRegistration; + DynamicServiceCompiler.NotifyAssemblyUnregistration = DynamicAssemblyRegistrationService.RequestAssemblyUnregistration; await app.InitializeApplicationAsync(); await app.RunAsync(); return 0; diff --git a/ui/src/routes/route.constant.ts b/ui/src/routes/route.constant.ts index e8bf99b..dd76532 100644 --- a/ui/src/routes/route.constant.ts +++ b/ui/src/routes/route.constant.ts @@ -43,6 +43,8 @@ export const ROUTES_ENUM = { componentsView: '/admin/developerkit/components/view/:id', componentsEdit: '/admin/developerkit/components/edit/:id', dynamicServices: '/admin/developerkit/dynamic-services', + dynamicServicesNew: '/admin/developerkit/dynamic-services/new', + dynamicServicesEdit: '/admin/developerkit/dynamic-services/edit/:id', }, reports: { generator: '/admin/reports/generator', diff --git a/ui/src/services/dynamicService.service.ts b/ui/src/services/dynamicService.service.ts index a0863c1..144c40b 100644 --- a/ui/src/services/dynamicService.service.ts +++ b/ui/src/services/dynamicService.service.ts @@ -54,6 +54,7 @@ export interface PublishDto { displayName?: string description?: string primaryEntityType?: string + isActive?: boolean } export interface DynamicAppServiceListResult { diff --git a/ui/src/views/developerKit/ComponentEditor.tsx b/ui/src/views/developerKit/ComponentEditor.tsx index ce5989d..0decb2c 100644 --- a/ui/src/views/developerKit/ComponentEditor.tsx +++ b/ui/src/views/developerKit/ComponentEditor.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useCallback } from 'react' -import { useParams, useNavigate } from 'react-router-dom' +import { useParams, useNavigate, Link } from 'react-router-dom' import { useComponents } from '../../contexts/ComponentContext' import { FaRegSave, @@ -211,26 +211,25 @@ export default ${pascalCaseName}Component;`
- +
-

+

{isEditing ? `${translate('::App.DeveloperKit.ComponentEditor.Title.Edit')} - ${values.name || initialValues.name || 'Component'}` : translate('::App.DeveloperKit.ComponentEditor.Title.Create')}

-

+

{isEditing ? 'Modify your React component' : 'Create a new React component'}

@@ -243,7 +242,7 @@ export default ${pascalCaseName}Component;` type="button" onClick={submitForm} disabled={isSubmitting || !values.name.trim() || !isValid} - className="flex items-center gap-1 bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800 text-white font-semibold px-2 py-1.5 rounded shadow transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed text-sm" + className="flex items-center gap-2 bg-emerald-600 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" > {isSubmitting @@ -255,7 +254,7 @@ export default ${pascalCaseName}Component;`
-
+ {/* Left Side - Component Settings */}
@@ -325,7 +324,7 @@ export default ${pascalCaseName}Component;` component={Input} placeholder="React component code goes here" textArea={true} - rows={5} + rows={10} onChange={(e: React.ChangeEvent) => { setFieldValue('code', e.target.value) }} diff --git a/ui/src/views/developerKit/DynamicServiceEditor.tsx b/ui/src/views/developerKit/DynamicServiceEditor.tsx index c1e2a0d..cc4b641 100644 --- a/ui/src/views/developerKit/DynamicServiceEditor.tsx +++ b/ui/src/views/developerKit/DynamicServiceEditor.tsx @@ -1,53 +1,31 @@ -import React, { useState, useRef, useEffect } from 'react' +import React, { useState, useEffect } from 'react' +import { useParams, useNavigate, Link } from 'react-router-dom' import { Editor } from '@monaco-editor/react' import { FaPlay, - FaUpload, - FaCode, + FaCopy, FaCheckCircle, FaExclamationCircle, FaSpinner, - FaCopy, FaExternalLinkAlt, - FaTrash, - FaSync, + FaArrowLeft, + FaCog, + FaCode, + FaSave, } from 'react-icons/fa' import { useLocalization } from '@/utils/hooks/useLocalization' import { dynamicServiceService, type CompileResult, type PublishResult, - type DynamicServiceDto, postTestCompile, - TestCompileDto, + type TestCompileDto, } from '@/services/dynamicService.service' import { Helmet } from 'react-helmet' import { APP_NAME } from '@/constants/app.constant' +import { ROUTES_ENUM } from '@/routes/route.constant' -const DynamicAppServiceEditor: React.FC = () => { - // State - const [code, setCode] = useState('') - const [serviceName, setServiceName] = useState('') - const [displayName, setDisplayName] = useState('') - const [description, setDescription] = useState('') - const [primaryEntityType, setPrimaryEntityType] = useState('') - - const [isCompiling, setIsCompiling] = useState(false) - const [isPublishing, setIsPublishing] = useState(false) - const [isLoading, setIsLoading] = useState(false) - - const [compileResult, setCompileResult] = useState(null) - const [publishResult, setPublishResult] = useState(null) - - const [services, setServices] = useState([]) - const [selectedService, setSelectedService] = useState(null) - - const [showServiceList, setShowServiceList] = useState(true) - - const { translate } = useLocalization() - - // Template kod - const defaultTemplate = `using System; +const defaultTemplate = `using System; using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Services; @@ -59,14 +37,6 @@ namespace DynamicServices [Authorize] public class DynamicCustomerAppService : ApplicationService { - // Repository injection örneği (kendi entity'nizi kullanın) - // private readonly IRepository _customerRepository; - - // public DynamicCustomerAppService(IRepository customerRepository) - // { - // _customerRepository = customerRepository; - // } - public virtual async Task GetHelloWorldAsync() { return await Task.FromResult("Hello World from Dynamic AppService!"); @@ -81,22 +51,29 @@ namespace DynamicServices "Item 3" }); } - - // Repository kullanım örneği: - // public virtual async Task> GetCustomersAsync() - // { - // return await _customerRepository.GetListAsync(); - // } } }` - // Component mount - useEffect(() => { - setCode(defaultTemplate) - loadServices() - }, []) +const DynamicServiceEditor: React.FC = () => { + const { id } = useParams<{ id: string }>() + const navigate = useNavigate() + const { translate } = useLocalization() + + const [code, setCode] = useState(defaultTemplate) + const [serviceName, setServiceName] = useState('') + const [displayName, setDisplayName] = useState('') + const [description, setDescription] = useState('') + const [primaryEntityType, setPrimaryEntityType] = useState('') + const [isActive, setIsActive] = useState(true) + const [submitted, setSubmitted] = useState(false) + + const [isCompiling, setIsCompiling] = useState(false) + const [isPublishing, setIsPublishing] = useState(false) + const [isLoading, setIsLoading] = useState(false) + + const [compileResult, setCompileResult] = useState(null) + const [publishResult, setPublishResult] = useState(null) - // Monaco Editor ayarları const editorOptions = { fontSize: 14, lineNumbers: 'on' as const, @@ -106,39 +83,42 @@ namespace DynamicServices minimap: { enabled: false }, folding: true, wordWrap: 'on' as const, - theme: 'vs-dark', } - // Servisleri yükle - const loadServices = async () => { + useEffect(() => { + if (id) { + loadService(id) + } + }, [id]) + + const loadService = async (serviceId: string) => { try { setIsLoading(true) - const response = await dynamicServiceService.getList() - setServices(response.items || []) + const data = await dynamicServiceService.getById(serviceId) + setCode(data.code) + setServiceName(data.name) + setDisplayName(data.displayName || '') + setDescription(data.description || '') + setPrimaryEntityType(data.primaryEntityType || '') + setIsActive(data.isActive ?? true) } catch (error) { - console.error('Servisler yüklenirken hata:', error) + console.error('Servis yüklenirken hata:', error) } finally { setIsLoading(false) } } - // Test compile const handleTestCompile = async () => { if (!code.trim()) { alert('Lütfen kod girin') return } - try { setIsCompiling(true) setCompileResult(null) - console.log('Test compile code:', code) - const input = { code: code } as TestCompileDto - const result = await postTestCompile(input) + const result = await postTestCompile({ code } as TestCompileDto) setCompileResult(result.data) } catch (error: any) { - console.error('Test compile error:', error) - console.error('Error response:', error.response?.data) setCompileResult({ success: false, errorMessage: error.response?.data?.message || 'Derleme sırasında hata oluştu', @@ -151,34 +131,36 @@ namespace DynamicServices } } - // Publish const handlePublish = async () => { + setSubmitted(true) if (!code.trim() || !serviceName.trim()) { - alert('Lütfen kod ve servis adını girin') return } - + if (isPublishing) return try { setIsPublishing(true) setPublishResult(null) - const requestData = { - name: serviceName, - code: code, - displayName: displayName, - description: description, - primaryEntityType: primaryEntityType, + // Edit modunda: önce eskiyi sil, sonra yeniden yayınla + if (id) { + await dynamicServiceService.delete(id) } - const result = await dynamicServiceService.publish(requestData) - setPublishResult(result) + const result = await dynamicServiceService.publish({ + name: serviceName, + code, + displayName, + description, + primaryEntityType, + isActive, + }) if (result.success) { - await loadServices() // Listeyi yenile + navigate(ROUTES_ENUM.protected.saas.developerKit.dynamicServices) + } else { + setPublishResult(result) } } catch (error: any) { - console.error('Publish error:', error) - console.error('Error response:', error.response?.data) setPublishResult({ success: false, errorMessage: error.response?.data?.message || 'Yayınlama sırasında hata oluştu', @@ -188,417 +170,228 @@ namespace DynamicServices } } - // Servisi yükle - const loadService = async (service: DynamicServiceDto) => { - try { - const data = await dynamicServiceService.getById(service.id) - - setSelectedService(data) - setCode(data.code) - setServiceName(data.name) - setDisplayName(data.displayName || '') - setDescription(data.description || '') - setPrimaryEntityType(data.primaryEntityType || '') - - setCompileResult(null) - setPublishResult(null) - } catch (error) { - console.error('Servis yüklenirken hata:', error) - } - } - - // Servisi sil - const deleteService = async (serviceId: string) => { - if (!confirm('Bu servisi silmek istediğinizden emin misiniz?')) { - return - } - - try { - await dynamicServiceService.delete(serviceId) - await loadServices() - - if (selectedService?.id === serviceId) { - setSelectedService(null) - setCode(defaultTemplate) - setServiceName('') - setDisplayName('') - setDescription('') - setPrimaryEntityType('') - } - } catch (error) { - console.error('Servis silinirken hata:', error) - alert('Servis silinirken hata oluştu') - } - } - - // Yeni servis - const newService = () => { - setSelectedService(null) - setCode(defaultTemplate) - setServiceName('') - setDisplayName('') - setDescription('') - setPrimaryEntityType('') - setCompileResult(null) - setPublishResult(null) - } - - // Swagger aç - const openSwagger = () => { - window.open(`${import.meta.env.VITE_API_URL}/swagger/index.html`, '_blank') - } - - // Kodu kopyala const copyCode = () => { navigator.clipboard.writeText(code) alert('Kod panoya kopyalandı') } + const pageTitle = id ? `Servis Düzenle` : `Yeni Servis` + + if (isLoading) { + return ( +
+ +
+ ) + } + + const serviceNameError = submitted && !serviceName.trim() + return (
- + {/* Header */} -
-
-

- {translate('::App.DeveloperKit.DynamicServices')} -

-

- {translate('::App.DeveloperKit.DynamicServices.Description')} -

-
-
- - +
+
+ {/* Left: back + icon + title */} +
+ + + Servislere Dön + +
+
+ +
+
+

{pageTitle}

+

+ {id ? 'Mevcut servisi düzenleyin' : 'Yeni bir dynamic servis oluşturun'} +

+
+
+ + {/* Right: action buttons + swagger + publish */} +
+ + + + + +
-
- {/* Service List */} - {showServiceList && ( -
-
-
-
-

Mevcut Servisler

-
- - -
-
-
- -
- {isLoading ? ( -
- - Yükleniyor... -
- ) : services.length > 0 ? ( - services.map((service) => ( -
loadService(service)} - > -
-
-

{service.name}

- {service.displayName && ( -

{service.displayName}

- )} -
- - {service.compilationStatus} - - v{service.version} -
-
- -
-
- )) - ) : ( -
Henüz servis yok
- )} -
-
-
- )} - - {/* Main Editor */} -
- {/* Service Info Form */} -
-
-
- - setServiceName(e.target.value)} - placeholder="ör: DynamicCustomerAppService" - className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" - /> -
-
- - setDisplayName(e.target.value)} - placeholder="ör: Müşteri Yönetimi" - className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" - /> -
-
- -