Developer Kits Dynamic Services düzeltildi

This commit is contained in:
Sedat ÖZTÜRK 2025-11-05 14:48:08 +03:00
parent 7c4bfd4b41
commit ce84b3baac
9 changed files with 392 additions and 395 deletions

View file

@ -685,6 +685,18 @@
"en": "Custom Components", "en": "Custom Components",
"tr": "Özel Bileşenler" "tr": "Özel Bileşenler"
}, },
{
"resourceName": "Platform",
"key": "App.DeveloperKit.DynamicServices",
"en": "Dynamic Services",
"tr": "Dinamik Servisler"
},
{
"resourceName": "Platform",
"key": "App.DeveloperKit.DynamicServices.Description",
"en": "Create and publish dynamic AppServices by writing C# code",
"tr": "C# kod yazarak dinamik AppService'ler oluşturun ve yayınlayın"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.Forum", "key": "App.Forum",
@ -9913,12 +9925,6 @@
"en": "fields", "en": "fields",
"tr": "alan" "tr": "alan"
}, },
{
"resourceName": "Platform",
"key": "App.DeveloperKit.DynamicServices",
"en": "Dynamic Services",
"tr": "Dinamik Hizmetler"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.DeveloperKit.Migration.Generating", "key": "App.DeveloperKit.Migration.Generating",

View file

@ -12,7 +12,6 @@ public static class TableNameResolver
_map = new(StringComparer.OrdinalIgnoreCase) _map = new(StringComparer.OrdinalIgnoreCase)
{ {
// 🔹 MODULE TABLOLARI // 🔹 MODULE TABLOLARI
{ nameof(TableNameEnum.DynamicService), (TablePrefix.PlatformByName, MenuPrefix.Platform) },
{ nameof(TableNameEnum.LogEntry), (TablePrefix.PlatformByName, MenuPrefix.Platform) }, { nameof(TableNameEnum.LogEntry), (TablePrefix.PlatformByName, MenuPrefix.Platform) },
{ nameof(TableNameEnum.Language), (TablePrefix.PlatformByName, MenuPrefix.Platform) }, { nameof(TableNameEnum.Language), (TablePrefix.PlatformByName, MenuPrefix.Platform) },
{ nameof(TableNameEnum.LanguageKey), (TablePrefix.PlatformByName, MenuPrefix.Platform) }, { nameof(TableNameEnum.LanguageKey), (TablePrefix.PlatformByName, MenuPrefix.Platform) },
@ -57,6 +56,7 @@ public static class TableNameResolver
{ 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) },
{ nameof(TableNameEnum.DynamicService), (TablePrefix.TenantByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.ReportCategory), (TablePrefix.TenantByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.ReportCategory), (TablePrefix.TenantByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.ReportTemplate), (TablePrefix.TenantByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.ReportTemplate), (TablePrefix.TenantByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.ReportParameter), (TablePrefix.TenantByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.ReportParameter), (TablePrefix.TenantByName, MenuPrefix.Saas) },

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Kurs.Platform.Migrations namespace Kurs.Platform.Migrations
{ {
[DbContext(typeof(PlatformDbContext))] [DbContext(typeof(PlatformDbContext))]
[Migration("20251105082644_Initial")] [Migration("20251105111749_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -3648,7 +3648,7 @@ namespace Kurs.Platform.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("Plat_H_DynamicService", (string)null); b.ToTable("Sas_T_DynamicService", (string)null);
}); });
modelBuilder.Entity("Kurs.Platform.Entities.EducationStatus", b => modelBuilder.Entity("Kurs.Platform.Entities.EducationStatus", b =>

View file

@ -1477,37 +1477,6 @@ namespace Kurs.Platform.Migrations
table.UniqueConstraint("AK_Plat_H_BackgroundWorker_MailQueueTableFormat_TableName", x => x.TableName); table.UniqueConstraint("AK_Plat_H_BackgroundWorker_MailQueueTableFormat_TableName", x => x.TableName);
}); });
migrationBuilder.CreateTable(
name: "Plat_H_DynamicService",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
DisplayName = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
Code = table.Column<string>(type: "text", nullable: false),
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: true),
CompilationStatus = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
LastCompilationError = table.Column<string>(type: "text", nullable: true),
LastSuccessfulCompilation = table.Column<DateTime>(type: "datetime2", nullable: true),
Version = table.Column<int>(type: "int", nullable: false, defaultValue: 1),
CodeHash = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
PrimaryEntityType = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
ControllerName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, 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),
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_Plat_H_DynamicService", x => x.Id);
});
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "Plat_H_Language", name: "Plat_H_Language",
columns: table => new columns: table => new
@ -2186,6 +2155,37 @@ namespace Kurs.Platform.Migrations
table.PrimaryKey("PK_Sas_T_CustomEntity", x => x.Id); table.PrimaryKey("PK_Sas_T_CustomEntity", x => x.Id);
}); });
migrationBuilder.CreateTable(
name: "Sas_T_DynamicService",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
DisplayName = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
Code = table.Column<string>(type: "text", nullable: false),
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: true),
CompilationStatus = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
LastCompilationError = table.Column<string>(type: "text", nullable: true),
LastSuccessfulCompilation = table.Column<DateTime>(type: "datetime2", nullable: true),
Version = table.Column<int>(type: "int", nullable: false, defaultValue: 1),
CodeHash = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
PrimaryEntityType = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
ControllerName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, 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),
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_DynamicService", x => x.Id);
});
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "Sas_T_GlobalSearch", name: "Sas_T_GlobalSearch",
columns: table => new columns: table => new
@ -6370,9 +6370,6 @@ namespace Kurs.Platform.Migrations
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "Plat_H_BackgroundWorker_MailQueueEvents"); name: "Plat_H_BackgroundWorker_MailQueueEvents");
migrationBuilder.DropTable(
name: "Plat_H_DynamicService");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "Plat_H_LanguageText"); name: "Plat_H_LanguageText");
@ -6454,6 +6451,9 @@ namespace Kurs.Platform.Migrations
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "Sas_T_CustomEntityField"); name: "Sas_T_CustomEntityField");
migrationBuilder.DropTable(
name: "Sas_T_DynamicService");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "Sas_T_GlobalSearch"); name: "Sas_T_GlobalSearch");

View file

@ -3645,7 +3645,7 @@ namespace Kurs.Platform.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("Plat_H_DynamicService", (string)null); b.ToTable("Sas_T_DynamicService", (string)null);
}); });
modelBuilder.Entity("Kurs.Platform.Entities.EducationStatus", b => modelBuilder.Entity("Kurs.Platform.Entities.EducationStatus", b =>

View file

@ -92,7 +92,7 @@ const ComponentManager: React.FC = () => {
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<div> <div>
<h1 className="text-2xl font-bold text-slate-900"> <h1 className="text-2xl font-bold text-slate-900">
{translate('::App.DeveloperKit.Component')} {translate('::App.DeveloperKit.Components')}
</h1> </h1>
<p className="text-slate-600">{translate('::App.DeveloperKit.Component.Description')}</p> <p className="text-slate-600">{translate('::App.DeveloperKit.Component.Description')}</p>
</div> </div>

View file

@ -19,7 +19,7 @@ import {
type PublishResult, type PublishResult,
type DynamicServiceDto, type DynamicServiceDto,
postTestCompile, postTestCompile,
TestCompileRequestDto, TestCompileDto,
} from '@/services/dynamicService.service' } from '@/services/dynamicService.service'
const DynamicAppServiceEditor: React.FC = () => { const DynamicAppServiceEditor: React.FC = () => {
@ -131,10 +131,9 @@ namespace DynamicServices
setIsCompiling(true) setIsCompiling(true)
setCompileResult(null) setCompileResult(null)
console.log('Test compile code:', code) console.log('Test compile code:', code)
const input = { code: code } as TestCompileRequestDto const input = { code: code } as TestCompileDto
const result = await postTestCompile(input) const result = await postTestCompile(input)
setCompileResult(result.data) setCompileResult(result.data)
} catch (error: any) { } catch (error: any) {
console.error('Test compile error:', error) console.error('Test compile error:', error)
console.error('Error response:', error.response?.data) console.error('Error response:', error.response?.data)
@ -193,14 +192,14 @@ namespace DynamicServices
const loadService = async (service: DynamicServiceDto) => { const loadService = async (service: DynamicServiceDto) => {
try { try {
const data = await dynamicServiceService.getById(service.id) const data = await dynamicServiceService.getById(service.id)
setSelectedService(data) setSelectedService(data)
setCode(data.code) setCode(data.code)
setServiceName(data.name) setServiceName(data.name)
setDisplayName(data.displayName || '') setDisplayName(data.displayName || '')
setDescription(data.description || '') setDescription(data.description || '')
setPrimaryEntityType(data.primaryEntityType || '') setPrimaryEntityType(data.primaryEntityType || '')
setCompileResult(null) setCompileResult(null)
setPublishResult(null) setPublishResult(null)
} catch (error) { } catch (error) {
@ -256,367 +255,363 @@ namespace DynamicServices
} }
return ( return (
<div className="min-h-screen bg-gray-50 p-4"> <div className="space-y-4">
<div className="mx-auto"> {/* Header */}
{/* Header */} <div className="flex items-center justify-between mb-4">
<div className="bg-white rounded-lg shadow-sm border p-6 mb-6"> <div>
<div className="flex items-center justify-between"> <h1 className="text-2xl font-bold text-slate-900">
<div> {translate('::App.DeveloperKit.DynamicServices')}
<h1 className="text-2xl font-bold text-gray-900 mb-2">Dynamic AppService Editor</h1> </h1>
<p className="text-gray-600"> <p className="text-slate-600">
C# kod yazarak dinamik AppService'ler oluşturun ve yayınlayın {translate('::App.DeveloperKit.DynamicServices.Description')}
</p> </p>
</div>
<div className="flex items-center gap-3">
<button
onClick={openSwagger}
className="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
>
<FaExternalLinkAlt className="w-4 h-4" />
Swagger
</button>
<button
onClick={() => setShowServiceList(!showServiceList)}
className="flex items-center gap-2 bg-gray-600 text-white px-4 py-2 rounded-lg hover:bg-gray-700 transition-colors"
>
<FaCode className="w-4 h-4" />
{showServiceList ? 'Listeyi Gizle' : 'Listeyi Göster'}
</button>
</div>
</div>
<div className="grid grid-cols-12 gap-6">
{/* Service List */}
{showServiceList && (
<div className="col-span-12 lg:col-span-4">
<div className="bg-white rounded-lg shadow-sm border">
<div className="p-4 border-b">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold">Mevcut Servisler</h3>
<div className="flex gap-2">
<button
onClick={newService}
className="bg-green-600 text-white px-3 py-1 rounded text-sm hover:bg-green-700"
>
<FaCode className="w-4 h-4 inline mr-1" />
Yeni
</button>
<button
onClick={loadServices}
className="bg-gray-600 text-white px-3 py-1 rounded text-sm hover:bg-gray-700"
>
<FaSync className="w-4 h-4" />
</button>
</div>
</div>
</div>
<div className="max-h-96 overflow-y-auto">
{isLoading ? (
<div className="p-4 text-center">
<FaSpinner className="w-6 h-6 animate-spin mx-auto mb-2" />
Yükleniyor...
</div>
) : services.length > 0 ? (
services.map((service) => (
<div
key={service.id}
className={`p-3 border-b hover:bg-gray-50 cursor-pointer ${
selectedService?.id === service.id
? 'bg-blue-50 border-l-4 border-l-blue-500'
: ''
}`}
onClick={() => loadService(service)}
>
<div className="flex items-center justify-between">
<div className="flex-1">
<h4 className="font-medium text-gray-900">{service.name}</h4>
{service.displayName && (
<p className="text-sm text-gray-600">{service.displayName}</p>
)}
<div className="flex items-center gap-2 mt-1">
<span
className={`px-2 py-1 text-xs rounded ${
service.compilationStatus === 'Success'
? 'bg-green-100 text-green-800'
: service.compilationStatus === 'Failed'
? 'bg-red-100 text-red-800'
: 'bg-yellow-100 text-yellow-800'
}`}
>
{service.compilationStatus}
</span>
<span className="text-xs text-gray-500">v{service.version}</span>
</div>
</div>
<button
onClick={(e) => {
e.stopPropagation()
deleteService(service.id)
}}
className="text-red-600 hover:text-red-800 p-1"
>
<FaTrash className="w-4 h-4" />
</button>
</div>
</div>
))
) : (
<div className="p-4 text-center text-gray-500">Henüz servis yok</div>
)}
</div>
</div> </div>
<div className="flex items-center gap-3"> </div>
)}
{/* Main Editor */}
<div className={`col-span-12 ${showServiceList ? 'lg:col-span-8' : ''}`}>
{/* Service Info Form */}
<div className="bg-white rounded-lg shadow-sm border p-4 mb-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Servis Adı *</label>
<input
type="text"
value={serviceName}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Görünen Ad</label>
<input
type="text"
value={displayName}
onChange={(e) => 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"
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">ıklama</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Bu servisin ne yaptığınııklayın..."
rows={2}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Ana Entity Türü
</label>
<input
type="text"
value={primaryEntityType}
onChange={(e) => setPrimaryEntityType(e.target.value)}
placeholder="ör: Customer"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="bg-white rounded-lg shadow-sm border p-4 mb-4">
<div className="flex flex-wrap items-center gap-3">
<button <button
onClick={openSwagger} onClick={handleTestCompile}
className="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors" disabled={isCompiling || !code.trim()}
className="flex items-center gap-2 bg-orange-600 text-white px-4 py-2 rounded-lg hover:bg-orange-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
> >
<FaExternalLinkAlt className="w-4 h-4" /> {isCompiling ? (
Swagger <FaSpinner className="w-4 h-4 animate-spin" />
) : (
<FaPlay className="w-4 h-4" />
)}
Test Compile
</button> </button>
<button <button
onClick={() => setShowServiceList(!showServiceList)} onClick={handlePublish}
disabled={isPublishing || !code.trim() || !serviceName.trim()}
className="flex items-center gap-2 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{isPublishing ? (
<FaSpinner className="w-4 h-4 animate-spin" />
) : (
<FaUpload className="w-4 h-4" />
)}
Publish
</button>
<button
onClick={copyCode}
className="flex items-center gap-2 bg-gray-600 text-white px-4 py-2 rounded-lg hover:bg-gray-700 transition-colors" className="flex items-center gap-2 bg-gray-600 text-white px-4 py-2 rounded-lg hover:bg-gray-700 transition-colors"
> >
<FaCode className="w-4 h-4" /> <FaCopy className="w-4 h-4" />
{showServiceList ? 'Listeyi Gizle' : 'Listeyi Göster'} Kopyala
</button> </button>
</div> </div>
</div> </div>
</div>
<div className="grid grid-cols-12 gap-6"> {/* Results */}
{/* Service List */} {(compileResult || publishResult) && (
{showServiceList && ( <div className="space-y-4 mb-4">
<div className="col-span-12 lg:col-span-4"> {/* Compile Result */}
<div className="bg-white rounded-lg shadow-sm border"> {compileResult && (
<div className="p-4 border-b"> <div
<div className="flex items-center justify-between"> className={`rounded-lg border p-4 ${
<h3 className="text-lg font-semibold">Mevcut Servisler</h3> compileResult.success
<div className="flex gap-2"> ? 'bg-green-50 border-green-200'
<button : 'bg-red-50 border-red-200'
onClick={newService} }`}
className="bg-green-600 text-white px-3 py-1 rounded text-sm hover:bg-green-700" >
> <div className="flex items-center gap-2 mb-2">
<FaCode className="w-4 h-4 inline mr-1" /> {compileResult.success ? (
Yeni <FaCheckCircle className="w-5 h-5 text-green-600" />
</button> ) : (
<button <FaExclamationCircle className="w-5 h-5 text-red-600" />
onClick={loadServices} )}
className="bg-gray-600 text-white px-3 py-1 rounded text-sm hover:bg-gray-700" <h4
> className={`font-medium ${
<FaSync className="w-4 h-4" /> compileResult.success ? 'text-green-800' : 'text-red-800'
</button> }`}
</div> >
Derleme {compileResult.success ? 'Başarılı' : 'Başarısız'}
</h4>
<span className="text-sm text-gray-600">
({compileResult.compilationTimeMs}ms)
</span>
</div> </div>
</div>
<div className="max-h-96 overflow-y-auto"> {!compileResult.success &&
{isLoading ? ( compileResult.errors &&
<div className="p-4 text-center"> compileResult.errors.length > 0 && (
<FaSpinner className="w-6 h-6 animate-spin mx-auto mb-2" /> <div className="space-y-2">
Yükleniyor... {compileResult.errors.map((error, index) => (
</div> <div key={index} className="bg-white rounded p-3 border">
) : services.length > 0 ? ( <div className="flex items-start gap-2">
services.map((service) => ( <span className="text-red-600 font-mono text-sm">{error.code}</span>
<div <div className="flex-1">
key={service.id} <p className="text-sm text-gray-800">{error.message}</p>
className={`p-3 border-b hover:bg-gray-50 cursor-pointer ${ <p className="text-xs text-gray-600 mt-1">
selectedService?.id === service.id Satır {error.line}, Sütun {error.column}
? 'bg-blue-50 border-l-4 border-l-blue-500' </p>
: '' </div>
}`}
onClick={() => loadService(service)}
>
<div className="flex items-center justify-between">
<div className="flex-1">
<h4 className="font-medium text-gray-900">{service.name}</h4>
{service.displayName && (
<p className="text-sm text-gray-600">{service.displayName}</p>
)}
<div className="flex items-center gap-2 mt-1">
<span
className={`px-2 py-1 text-xs rounded ${
service.compilationStatus === 'Success'
? 'bg-green-100 text-green-800'
: service.compilationStatus === 'Failed'
? 'bg-red-100 text-red-800'
: 'bg-yellow-100 text-yellow-800'
}`}
>
{service.compilationStatus}
</span>
<span className="text-xs text-gray-500">v{service.version}</span>
</div> </div>
</div> </div>
<button ))}
onClick={(e) => {
e.stopPropagation()
deleteService(service.id)
}}
className="text-red-600 hover:text-red-800 p-1"
>
<FaTrash className="w-4 h-4" />
</button>
</div>
</div> </div>
)) )}
) : (
<div className="p-4 text-center text-gray-500">Henüz servis yok</div> {compileResult.hasWarnings && compileResult.warnings && (
<div className="mt-3">
<h5 className="text-sm font-medium text-yellow-800 mb-2">Uyarılar:</h5>
<ul className="list-disc list-inside space-y-1">
{compileResult.warnings.map((warning, index) => (
<li key={index} className="text-sm text-yellow-700">
{warning}
</li>
))}
</ul>
</div>
)} )}
</div> </div>
</div> )}
{/* Publish Result */}
{publishResult && (
<div
className={`rounded-lg border p-4 ${
publishResult.success
? 'bg-green-50 border-green-200'
: 'bg-red-50 border-red-200'
}`}
>
<div className="flex items-center gap-2 mb-2">
{publishResult.success ? (
<FaCheckCircle className="w-5 h-5 text-green-600" />
) : (
<FaExclamationCircle className="w-5 h-5 text-red-600" />
)}
<h4
className={`font-medium ${
publishResult.success ? 'text-green-800' : 'text-red-800'
}`}
>
Yayınlama {publishResult.success ? 'Başarılı' : 'Başarısız'}
</h4>
</div>
{publishResult.success && (
<div className="space-y-2">
<p className="text-green-700 text-sm">
AppService başarıyla yayınlandı ve Swagger'a eklendi.
</p>
{publishResult.controllerName && (
<p className="text-sm text-gray-600">
Controller:{' '}
<code className="bg-gray-100 px-2 py-1 rounded">
{publishResult.controllerName}
</code>
</p>
)}
{publishResult.generatedEndpoints &&
publishResult.generatedEndpoints.length > 0 && (
<div>
<h5 className="text-sm font-medium text-gray-700 mb-1">
Oluşturulan Endpoint'ler:
</h5>
<ul className="list-disc list-inside space-y-1">
{publishResult.generatedEndpoints.map((endpoint, index) => (
<li key={index} className="text-sm text-gray-600 font-mono">
{endpoint}
</li>
))}
</ul>
</div>
)}
<div className="flex gap-2 mt-3">
<button
onClick={openSwagger}
className="flex items-center gap-1 bg-blue-600 text-white px-3 py-1 rounded text-sm hover:bg-blue-700"
>
<FaExternalLinkAlt className="w-3 h-3" />
Swagger'da Gör
</button>
</div>
</div>
)}
{!publishResult.success && publishResult.errorMessage && (
<p className="text-red-700 text-sm">{publishResult.errorMessage}</p>
)}
</div>
)}
</div> </div>
)} )}
{/* Main Editor */} {/* Monaco Editor */}
<div className={`col-span-12 ${showServiceList ? 'lg:col-span-8' : ''}`}> <div className="bg-white rounded-lg shadow-sm border overflow-hidden">
{/* Service Info Form */} <div className="p-3 bg-gray-50 border-b flex items-center justify-between">
<div className="bg-white rounded-lg shadow-sm border p-4 mb-4"> <h3 className="font-medium text-gray-700">C# Code Editor</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="flex items-center gap-2 text-sm text-gray-600">
<div> <span>Lines: {code.split('\n').length}</span>
<label className="block text-sm font-medium text-gray-700 mb-1"> <span>|</span>
Servis Adı * <span>Characters: {code.length}</span>
</label>
<input
type="text"
value={serviceName}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Görünen Ad</label>
<input
type="text"
value={displayName}
onChange={(e) => 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"
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">ıklama</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Bu servisin ne yaptığınııklayın..."
rows={2}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Ana Entity Türü
</label>
<input
type="text"
value={primaryEntityType}
onChange={(e) => setPrimaryEntityType(e.target.value)}
placeholder="ör: Customer"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div> </div>
</div> </div>
<div style={{ height: '600px' }}>
{/* Action Buttons */} <Editor
<div className="bg-white rounded-lg shadow-sm border p-4 mb-4"> defaultLanguage="csharp"
<div className="flex flex-wrap items-center gap-3"> value={code}
<button onChange={(value) => setCode(value || '')}
onClick={handleTestCompile} options={editorOptions}
disabled={isCompiling || !code.trim()} theme="vs-dark"
className="flex items-center gap-2 bg-orange-600 text-white px-4 py-2 rounded-lg hover:bg-orange-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" />
>
{isCompiling ? (
<FaSpinner className="w-4 h-4 animate-spin" />
) : (
<FaPlay className="w-4 h-4" />
)}
Test Compile
</button>
<button
onClick={handlePublish}
disabled={isPublishing || !code.trim() || !serviceName.trim()}
className="flex items-center gap-2 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{isPublishing ? (
<FaSpinner className="w-4 h-4 animate-spin" />
) : (
<FaUpload className="w-4 h-4" />
)}
Publish
</button>
<button
onClick={copyCode}
className="flex items-center gap-2 bg-gray-600 text-white px-4 py-2 rounded-lg hover:bg-gray-700 transition-colors"
>
<FaCopy className="w-4 h-4" />
Kopyala
</button>
</div>
</div>
{/* Results */}
{(compileResult || publishResult) && (
<div className="space-y-4 mb-4">
{/* Compile Result */}
{compileResult && (
<div
className={`rounded-lg border p-4 ${
compileResult.success
? 'bg-green-50 border-green-200'
: 'bg-red-50 border-red-200'
}`}
>
<div className="flex items-center gap-2 mb-2">
{compileResult.success ? (
<FaCheckCircle className="w-5 h-5 text-green-600" />
) : (
<FaExclamationCircle className="w-5 h-5 text-red-600" />
)}
<h4
className={`font-medium ${
compileResult.success ? 'text-green-800' : 'text-red-800'
}`}
>
Derleme {compileResult.success ? 'Başarılı' : 'Başarısız'}
</h4>
<span className="text-sm text-gray-600">
({compileResult.compilationTimeMs}ms)
</span>
</div>
{!compileResult.success &&
compileResult.errors &&
compileResult.errors.length > 0 && (
<div className="space-y-2">
{compileResult.errors.map((error, index) => (
<div key={index} className="bg-white rounded p-3 border">
<div className="flex items-start gap-2">
<span className="text-red-600 font-mono text-sm">{error.code}</span>
<div className="flex-1">
<p className="text-sm text-gray-800">{error.message}</p>
<p className="text-xs text-gray-600 mt-1">
Satır {error.line}, Sütun {error.column}
</p>
</div>
</div>
</div>
))}
</div>
)}
{compileResult.hasWarnings && compileResult.warnings && (
<div className="mt-3">
<h5 className="text-sm font-medium text-yellow-800 mb-2">Uyarılar:</h5>
<ul className="list-disc list-inside space-y-1">
{compileResult.warnings.map((warning, index) => (
<li key={index} className="text-sm text-yellow-700">
{warning}
</li>
))}
</ul>
</div>
)}
</div>
)}
{/* Publish Result */}
{publishResult && (
<div
className={`rounded-lg border p-4 ${
publishResult.success
? 'bg-green-50 border-green-200'
: 'bg-red-50 border-red-200'
}`}
>
<div className="flex items-center gap-2 mb-2">
{publishResult.success ? (
<FaCheckCircle className="w-5 h-5 text-green-600" />
) : (
<FaExclamationCircle className="w-5 h-5 text-red-600" />
)}
<h4
className={`font-medium ${
publishResult.success ? 'text-green-800' : 'text-red-800'
}`}
>
Yayınlama {publishResult.success ? 'Başarılı' : 'Başarısız'}
</h4>
</div>
{publishResult.success && (
<div className="space-y-2">
<p className="text-green-700 text-sm">
AppService başarıyla yayınlandı ve Swagger'a eklendi.
</p>
{publishResult.controllerName && (
<p className="text-sm text-gray-600">
Controller:{' '}
<code className="bg-gray-100 px-2 py-1 rounded">
{publishResult.controllerName}
</code>
</p>
)}
{publishResult.generatedEndpoints &&
publishResult.generatedEndpoints.length > 0 && (
<div>
<h5 className="text-sm font-medium text-gray-700 mb-1">
Oluşturulan Endpoint'ler:
</h5>
<ul className="list-disc list-inside space-y-1">
{publishResult.generatedEndpoints.map((endpoint, index) => (
<li key={index} className="text-sm text-gray-600 font-mono">
{endpoint}
</li>
))}
</ul>
</div>
)}
<div className="flex gap-2 mt-3">
<button
onClick={openSwagger}
className="flex items-center gap-1 bg-blue-600 text-white px-3 py-1 rounded text-sm hover:bg-blue-700"
>
<FaExternalLinkAlt className="w-3 h-3" />
Swagger'da Gör
</button>
</div>
</div>
)}
{!publishResult.success && publishResult.errorMessage && (
<p className="text-red-700 text-sm">{publishResult.errorMessage}</p>
)}
</div>
)}
</div>
)}
{/* Monaco Editor */}
<div className="bg-white rounded-lg shadow-sm border overflow-hidden">
<div className="p-3 bg-gray-50 border-b flex items-center justify-between">
<h3 className="font-medium text-gray-700">C# Code Editor</h3>
<div className="flex items-center gap-2 text-sm text-gray-600">
<span>Lines: {code.split('\n').length}</span>
<span>|</span>
<span>Characters: {code.length}</span>
</div>
</div>
<div style={{ height: '600px' }}>
<Editor
defaultLanguage="csharp"
value={code}
onChange={(value) => setCode(value || '')}
options={editorOptions}
theme="vs-dark"
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -162,8 +162,8 @@ const MigrationManager: React.FC = () => {
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<div> <div>
<h1 className="text-xl font-bold text-slate-900 mb-2"> <h1 className="text-2xl font-bold text-slate-900">
{translate('::App.DeveloperKit.Migration')} {translate('::App.DeveloperKit.Migrations')}
</h1> </h1>
<p className="text-slate-600">{translate('::App.DeveloperKit.Migration.Description')}</p> <p className="text-slate-600">{translate('::App.DeveloperKit.Migration.Description')}</p>
</div> </div>

View file

@ -48,10 +48,6 @@ export interface TestCompileDto {
code: string code: string
} }
export interface TestCompileRequestDto {
Code: string
}
export interface PublishDto { export interface PublishDto {
name: string name: string
code: string code: string
@ -65,7 +61,7 @@ export interface DynamicAppServiceListResult {
totalCount: number totalCount: number
} }
export const postTestCompile = (input: TestCompileRequestDto) => export const postTestCompile = (input: TestCompileDto) =>
apiService.fetchData<CompileResult>({ apiService.fetchData<CompileResult>({
method: 'POST', method: 'POST',
url: `/api/app/dynamic-app-service/test-compile`, url: `/api/app/dynamic-app-service/test-compile`,