Audit bilgileri yapılan değişiklikler kaydediliyor.
This commit is contained in:
parent
0f9fe71e6b
commit
a299ae099d
11 changed files with 451 additions and 196 deletions
|
|
@ -3,6 +3,7 @@ using Kurs.Notifications.Application;
|
|||
using Kurs.Settings;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.Account;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.AutoMapper;
|
||||
using Volo.Abp.FeatureManagement;
|
||||
using Volo.Abp.Identity;
|
||||
|
|
@ -39,5 +40,11 @@ public class PlatformApplicationModule : AbpModule
|
|||
options.IsDynamicPermissionStoreEnabled = true;
|
||||
options.SaveStaticPermissionsToDatabase = true;
|
||||
});
|
||||
|
||||
// ListFormCustomization için audit kaydı kapatılıyor
|
||||
Configure<AbpAuditingOptions>(options =>
|
||||
{
|
||||
options.IgnoredTypes.Add(typeof(Entities.ListFormCustomization));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ public class Sector : FullAuditedEntity<Guid>, IMultiTenant
|
|||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public string FullName { get; set; }
|
||||
|
||||
Guid? IMultiTenant.TenantId => TenantId;
|
||||
}
|
||||
|
|
@ -750,7 +750,6 @@ public class PlatformDbContext :
|
|||
b.ConfigureByConvention();
|
||||
|
||||
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.FullName).HasMaxLength(256);
|
||||
});
|
||||
|
||||
builder.Entity<ContactTag>(b =>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
|||
namespace Kurs.Platform.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlatformDbContext))]
|
||||
[Migration("20251016215302_Initial")]
|
||||
[Migration("20251018150230_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -6070,10 +6070,6 @@ namespace Kurs.Platform.Migrations
|
|||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("FullName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
|
|
@ -1068,7 +1068,6 @@ namespace Kurs.Platform.Migrations
|
|||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
FullName = 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),
|
||||
|
|
@ -6067,10 +6067,6 @@ namespace Kurs.Platform.Migrations
|
|||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("FullName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
|
|
|
|||
|
|
@ -100,7 +100,9 @@
|
|||
"props": null,
|
||||
"description": null,
|
||||
"isActive": true,
|
||||
"dependencies": ["AxiosListComponent"]
|
||||
"dependencies": [
|
||||
"AxiosListComponent"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ReportCategories": [
|
||||
|
|
@ -120,7 +122,6 @@
|
|||
"icon": "📜"
|
||||
}
|
||||
],
|
||||
|
||||
"Abouts": [
|
||||
{
|
||||
"stats": [
|
||||
|
|
@ -623,65 +624,97 @@
|
|||
],
|
||||
"Sectors": [
|
||||
{
|
||||
"Name": "Ambalaj",
|
||||
"FullName": ""
|
||||
"Name": "Adalet ve Güvenlik"
|
||||
},
|
||||
{
|
||||
"Name": "Demir Çelik",
|
||||
"FullName": ""
|
||||
"Name": "Ağaç İşleri, Kağıt ve Kağıt Ürünleri"
|
||||
},
|
||||
{
|
||||
"Name": "Ambalaj"
|
||||
},
|
||||
{
|
||||
"Name": "Bilişim Teknolojileri"
|
||||
},
|
||||
{
|
||||
"Name": "Cam, Çimento ve Toprak"
|
||||
},
|
||||
{
|
||||
"Name": "Çevre"
|
||||
},
|
||||
{
|
||||
"Name": "Demir Çelik"
|
||||
},
|
||||
{
|
||||
"Name": "Diğer",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Eğitim"
|
||||
},
|
||||
{
|
||||
"Name": "Elektrik ve Elektronik",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Giyim",
|
||||
"Name": "Enerji"
|
||||
},
|
||||
{
|
||||
"Name": "Finans",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Güvenlik",
|
||||
"FullName": ""
|
||||
"Name": "Gıda"
|
||||
},
|
||||
{
|
||||
"Name": "Gıda",
|
||||
"FullName": ""
|
||||
"Name": "Giyim"
|
||||
},
|
||||
{
|
||||
"Name": "Güvenlik"
|
||||
},
|
||||
{
|
||||
"Name": "Hizmet-servis",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Hırdavat ve Nalburiye",
|
||||
"FullName": ""
|
||||
"Name": "Hırdavat ve Nalburiye"
|
||||
},
|
||||
{
|
||||
"Name": "Isıtma, Soğutma ve Havalandırma",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "İnşaat",
|
||||
"FullName": ""
|
||||
"Name": "İnşaat"
|
||||
},
|
||||
{
|
||||
"Name": "İş ve Yönetim"
|
||||
},
|
||||
{
|
||||
"Name": "Kantar",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Kimya, Petrol, Lastik ve Plastik"
|
||||
},
|
||||
{
|
||||
"Name": "Kimyasal",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Kırtasiye",
|
||||
"FullName": ""
|
||||
"Name": "Kırtasiye"
|
||||
},
|
||||
{
|
||||
"Name": "Kültür, Sanat ve Tasarım"
|
||||
},
|
||||
{
|
||||
"Name": "Laboratuar ve Test Ürünleri",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Maden"
|
||||
},
|
||||
{
|
||||
"Name": "Makine"
|
||||
},
|
||||
{
|
||||
"Name": "Makina",
|
||||
"FullName": ""
|
||||
|
|
@ -690,37 +723,67 @@
|
|||
"Name": "Matbaa",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Medya, İletişim ve Yayıncılık"
|
||||
},
|
||||
{
|
||||
"Name": "Metal"
|
||||
},
|
||||
{
|
||||
"Name": "Ofis",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Otomotiv"
|
||||
},
|
||||
{
|
||||
"Name": "Oto Tamir-Servis",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Pnomatik",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Sarf",
|
||||
"FullName": ""
|
||||
"Name": "Pnomatik"
|
||||
},
|
||||
{
|
||||
"Name": "Sağlık",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Tartı",
|
||||
"Name": "Sağlık ve Sosyal Hizmetler"
|
||||
},
|
||||
{
|
||||
"Name": "Sarf"
|
||||
},
|
||||
{
|
||||
"Name": "Spor ve Rekreasyon",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Transpalet",
|
||||
"Name": "Tarım, Avcılık ve Balıkçılık"
|
||||
},
|
||||
{
|
||||
"Name": "Tartı"
|
||||
},
|
||||
{
|
||||
"Name": "Tekstil, Hazır Giyim, Deri",
|
||||
"FullName": ""
|
||||
},
|
||||
{
|
||||
"Name": "Yedek Parça",
|
||||
"FullName": ""
|
||||
"Name": "Ticaret (Satış ve Pazarlama)"
|
||||
},
|
||||
{
|
||||
"Name": "Toplumsal ve Kişisel Hizmetler"
|
||||
},
|
||||
{
|
||||
"Name": "Transpalet"
|
||||
},
|
||||
{
|
||||
"Name": "Turizm, Konaklama, Yiyecek-İçecek Hizmetleri"
|
||||
},
|
||||
{
|
||||
"Name": "Ulaştırma, Lojistik ve Haberleşme"
|
||||
},
|
||||
{
|
||||
"Name": "Yedek Parça"
|
||||
}
|
||||
],
|
||||
"SkillTypes": [
|
||||
|
|
|
|||
|
|
@ -192,7 +192,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
await _sectorRepository.InsertAsync(new Sector
|
||||
{
|
||||
Name = item.Name,
|
||||
FullName = item.FullName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ public class GlobalSearchSeedDto
|
|||
public class SectorSeedDto
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FullName { get; set; }
|
||||
}
|
||||
|
||||
public class UomCategorySeedDto
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ using OpenIddict.Server.AspNetCore;
|
|||
using OpenIddict.Validation.AspNetCore;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Account.Web;
|
||||
using Volo.Abp.AspNetCore.Auditing;
|
||||
using Volo.Abp.AspNetCore.ExceptionHandling;
|
||||
using Volo.Abp.AspNetCore.MultiTenancy;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
|
|
@ -35,6 +36,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
|
|||
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic;
|
||||
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling;
|
||||
using Volo.Abp.AspNetCore.Serilog;
|
||||
using Volo.Abp.Auditing;
|
||||
using Volo.Abp.Autofac;
|
||||
using Volo.Abp.BackgroundWorkers.Hangfire;
|
||||
using Volo.Abp.BlobStoring;
|
||||
|
|
@ -112,6 +114,7 @@ public class PlatformHttpApiHostModule : AbpModule
|
|||
ConfigureCache();
|
||||
ConfigureHangfire(context, configuration);
|
||||
ConfigureBlobStoring(configuration);
|
||||
ConfigureAuditing();
|
||||
|
||||
context.Services.AddSignalR();
|
||||
|
||||
|
|
@ -351,6 +354,13 @@ public class PlatformHttpApiHostModule : AbpModule
|
|||
});
|
||||
}
|
||||
|
||||
private void ConfigureAuditing()
|
||||
{
|
||||
Configure<AbpAspNetCoreAuditingOptions>(options =>
|
||||
{
|
||||
options.IgnoredUrls.Add("/api/app/list-form-customization");
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnApplicationInitialization(ApplicationInitializationContext context)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,10 +3,19 @@ import Dialog from '@/components/ui/Dialog'
|
|||
import TabList from '@/components/ui/Tabs/TabList'
|
||||
import TabNav from '@/components/ui/Tabs/TabNav'
|
||||
import TabContent from '@/components/ui/Tabs/TabContent'
|
||||
import { Tabs } from '@/components/ui'
|
||||
import { Tabs, Badge, Spinner } from '@/components/ui'
|
||||
import { AdaptableCard } from '@/components/shared'
|
||||
import { AuditLogDto } from '@/proxy/auditLog/audit-log'
|
||||
import { getAuditLogs } from '@/services/identity.service'
|
||||
import {
|
||||
HiOutlineClock,
|
||||
HiOutlineGlobe,
|
||||
HiOutlineUser,
|
||||
HiOutlineCode,
|
||||
HiOutlineDocumentText,
|
||||
HiOutlineExclamationCircle,
|
||||
HiOutlineCheckCircle,
|
||||
} from 'react-icons/hi'
|
||||
|
||||
function AuditLogs({
|
||||
open,
|
||||
|
|
@ -18,6 +27,7 @@ function AuditLogs({
|
|||
id: string
|
||||
}) {
|
||||
const [selectedLog, setSelectedLog] = useState<AuditLogDto>()
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (open && id) {
|
||||
|
|
@ -26,184 +36,362 @@ function AuditLogs({
|
|||
}, [open, id])
|
||||
|
||||
const fetchAuditLog = async (logId: string) => {
|
||||
const response = await getAuditLogs(logId)
|
||||
setSelectedLog(response.data)
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await getAuditLogs(logId)
|
||||
setSelectedLog(response.data)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch audit log:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog width="80%" isOpen={open} onClose={onDialogClose} onRequestClose={onDialogClose}>
|
||||
<h5 className="text-lg font-semibold mb-4">
|
||||
Audit Log Detail
|
||||
{selectedLog?.id && (
|
||||
<span className="text-xs font-normal text-slate-500 ml-2">({selectedLog.id})</span>
|
||||
)}
|
||||
</h5>
|
||||
const getStatusBadge = (statusCode?: number) => {
|
||||
if (!statusCode) return <Badge className="bg-gray-500">Unknown</Badge>
|
||||
if (statusCode >= 200 && statusCode < 300)
|
||||
return <Badge className="bg-green-500" content={statusCode}></Badge>
|
||||
if (statusCode >= 400 && statusCode < 500)
|
||||
return <Badge className="bg-yellow-500" content={statusCode}></Badge>
|
||||
if (statusCode >= 500) return <Badge className="bg-red-500" content={statusCode}></Badge>
|
||||
return <Badge className="bg-blue-500" content={statusCode}></Badge>
|
||||
}
|
||||
|
||||
{!selectedLog ? (
|
||||
<div className="text-center py-6 text-gray-500">Loading...</div>
|
||||
const getChangeTypeBadge = (changeType: number) => {
|
||||
const types = ['Created', 'Updated', 'Deleted']
|
||||
const colors = ['bg-green-600', 'bg-blue-600', 'bg-red-600']
|
||||
return (
|
||||
<Badge
|
||||
className={colors[changeType] || 'bg-gray-600'}
|
||||
content={types[changeType] || 'Unknown'}
|
||||
></Badge>
|
||||
)
|
||||
}
|
||||
|
||||
const formatDuration = (ms: number) => {
|
||||
if (ms < 1000) return `${ms}ms`
|
||||
return `${(ms / 1000).toFixed(2)}s`
|
||||
}
|
||||
|
||||
const InfoRow = ({ icon: Icon, label, value, valueClassName = '' }: any) => (
|
||||
<div className="flex items-start gap-3 py-2 border-b border-gray-100 last:border-0">
|
||||
<Icon className="w-5 h-5 text-gray-400 mt-0.5 flex-shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs font-medium text-gray-500 mb-0.5">{label}</div>
|
||||
<div className={`text-sm ${valueClassName} break-words`}>{value}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<Dialog width="90%" isOpen={open} onClose={onDialogClose} onRequestClose={onDialogClose}>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6 pb-4 border-b border-gray-200">
|
||||
<div>
|
||||
<h4 className="text-xl font-bold text-gray-800">Audit Log Details</h4>
|
||||
{selectedLog?.id && <p className="text-sm text-gray-500 mt-1">ID: {selectedLog.id}</p>}
|
||||
</div>
|
||||
{selectedLog?.httpStatusCode && (
|
||||
<div className="flex items-center gap-2">
|
||||
{getStatusBadge(selectedLog.httpStatusCode)}
|
||||
<Badge
|
||||
className="border border-gray-300 bg-white text-gray-700"
|
||||
content={selectedLog.applicationName}
|
||||
></Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-16">
|
||||
<Spinner size={40} />
|
||||
</div>
|
||||
) : !selectedLog ? (
|
||||
<div className="text-center py-16">
|
||||
<HiOutlineExclamationCircle className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||
<p className="text-gray-500">No audit log found</p>
|
||||
</div>
|
||||
) : (
|
||||
<Tabs defaultValue="log" variant="pill">
|
||||
<TabList className="flex-wrap border-b mb-4 bg-slate-50 rounded-t">
|
||||
<TabNav value="log">Log</TabNav>
|
||||
<TabList className="mb-6 bg-gray-50 p-1 rounded-lg">
|
||||
<TabNav value="log">
|
||||
<HiOutlineDocumentText className="w-4 h-4 mr-2" />
|
||||
Overview
|
||||
</TabNav>
|
||||
<TabNav value="actions">
|
||||
Actions {selectedLog.actions?.length > 0 && `(${selectedLog.actions.length})`}
|
||||
<HiOutlineCode className="w-4 h-4 mr-2" />
|
||||
Actions
|
||||
{selectedLog.actions?.length > 0 && (
|
||||
<Badge
|
||||
className="ml-2 bg-blue-500"
|
||||
content={`${selectedLog.actions.length}`}
|
||||
></Badge>
|
||||
)}
|
||||
</TabNav>
|
||||
<TabNav value="changes">
|
||||
Entity Changes{' '}
|
||||
{selectedLog.entityChanges?.length > 0 && `(${selectedLog.entityChanges.length})`}
|
||||
<HiOutlineCheckCircle className="w-4 h-4 mr-2" />
|
||||
Entity Changes
|
||||
{selectedLog.entityChanges?.length > 0 && (
|
||||
<Badge
|
||||
className="ml-2 bg-purple-500"
|
||||
content={`${selectedLog.entityChanges.length}`}
|
||||
></Badge>
|
||||
)}
|
||||
</TabNav>
|
||||
</TabList>
|
||||
|
||||
{/* LOG DETAILS */}
|
||||
{/* OVERVIEW TAB */}
|
||||
<TabContent value="log">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 p-2">
|
||||
<AdaptableCard>
|
||||
<h6 className="font-medium mb-3 text-slate-600 text-sm">Request Info</h6>
|
||||
<table className="w-full text-xs">
|
||||
<tbody className="[&>tr>*]:py-0.5 [&>tr>td:first-child]:font-semibold [&>tr>td:first-child]:w-40 [&>tr>td:last-child]:break-all">
|
||||
<tr>
|
||||
<td>User</td>
|
||||
<td>{selectedLog.userName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Time</td>
|
||||
<td>{new Date(selectedLog.executionTime).toLocaleString()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Duration</td>
|
||||
<td>{selectedLog.executionDuration} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP</td>
|
||||
<td>{selectedLog.clientIpAddress}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Method</td>
|
||||
<td>{selectedLog.httpMethod}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status Code</td>
|
||||
<td>{selectedLog.httpStatusCode}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>{selectedLog.url}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Request Information */}
|
||||
<AdaptableCard className="shadow-sm">
|
||||
<h6 className="font-semibold text-gray-700 mb-4 flex items-center gap-2">
|
||||
<HiOutlineGlobe className="w-5 h-5 text-blue-500" />
|
||||
Request Information
|
||||
</h6>
|
||||
<div className="space-y-1">
|
||||
<InfoRow
|
||||
icon={HiOutlineUser}
|
||||
label="User"
|
||||
value={selectedLog.userName || 'Anonymous'}
|
||||
valueClassName="font-medium text-gray-800"
|
||||
/>
|
||||
<InfoRow
|
||||
icon={HiOutlineClock}
|
||||
label="Execution Time"
|
||||
value={new Date(selectedLog.executionTime).toLocaleString('tr-TR', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
})}
|
||||
/>
|
||||
<InfoRow
|
||||
icon={HiOutlineClock}
|
||||
label="Duration"
|
||||
value={formatDuration(selectedLog.executionDuration)}
|
||||
valueClassName={
|
||||
selectedLog.executionDuration > 1000
|
||||
? 'text-orange-600 font-semibold'
|
||||
: 'text-green-600'
|
||||
}
|
||||
/>
|
||||
<InfoRow
|
||||
icon={HiOutlineGlobe}
|
||||
label="Client IP"
|
||||
value={selectedLog.clientIpAddress || 'Unknown'}
|
||||
/>
|
||||
</div>
|
||||
</AdaptableCard>
|
||||
|
||||
<AdaptableCard>
|
||||
<h6 className="font-medium mb-3 text-slate-600 text-sm">Client Info</h6>
|
||||
<table className="w-full text-xs">
|
||||
<tbody className="[&>tr>*]:py-0.5 [&>tr>td:first-child]:font-semibold [&>tr>td:first-child]:w-40 [&>tr>td:last-child]:break-all">
|
||||
<tr>
|
||||
<td>Browser</td>
|
||||
<td>{selectedLog.browserInfo}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Exceptions</td>
|
||||
<td>
|
||||
<pre className="bg-slate-100 text-red-500 p-2 rounded text-[11px] whitespace-pre-wrap leading-snug">
|
||||
{selectedLog.exceptions || 'None'}
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/* HTTP Details */}
|
||||
<AdaptableCard className="shadow-sm">
|
||||
<h6 className="font-semibold text-gray-700 mb-4 flex items-center gap-2">
|
||||
<HiOutlineCode className="w-5 h-5 text-green-500" />
|
||||
HTTP Details
|
||||
</h6>
|
||||
<div className="space-y-1">
|
||||
<InfoRow
|
||||
icon={HiOutlineCode}
|
||||
label="Method"
|
||||
value={
|
||||
<Badge
|
||||
className={
|
||||
selectedLog.httpMethod === 'POST'
|
||||
? 'bg-blue-500'
|
||||
: selectedLog.httpMethod === 'GET'
|
||||
? 'bg-green-500'
|
||||
: selectedLog.httpMethod === 'PUT'
|
||||
? 'bg-yellow-500'
|
||||
: selectedLog.httpMethod === 'DELETE'
|
||||
? 'bg-red-500'
|
||||
: 'bg-gray-500'
|
||||
}
|
||||
content={selectedLog.httpMethod}
|
||||
></Badge>
|
||||
}
|
||||
/>
|
||||
<InfoRow
|
||||
icon={HiOutlineCheckCircle}
|
||||
label="Status Code"
|
||||
value={getStatusBadge(selectedLog.httpStatusCode)}
|
||||
/>
|
||||
<InfoRow
|
||||
icon={HiOutlineGlobe}
|
||||
label="URL"
|
||||
value={selectedLog.url}
|
||||
valueClassName="text-blue-600 font-mono text-xs"
|
||||
/>
|
||||
<InfoRow
|
||||
icon={HiOutlineDocumentText}
|
||||
label="Browser"
|
||||
value={
|
||||
<span className="text-xs text-gray-600 line-clamp-2">
|
||||
{selectedLog.browserInfo || 'Unknown'}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</AdaptableCard>
|
||||
|
||||
{/* Exceptions (Full Width) */}
|
||||
{selectedLog.exceptions && (
|
||||
<AdaptableCard className="lg:col-span-2 shadow-sm border-l-4 border-red-500">
|
||||
<h6 className="font-semibold text-red-600 mb-3 flex items-center gap-2">
|
||||
<HiOutlineExclamationCircle className="w-5 h-5" />
|
||||
Exceptions
|
||||
</h6>
|
||||
<pre className="bg-red-50 text-red-700 p-4 rounded-lg text-xs whitespace-pre-wrap leading-relaxed font-mono overflow-x-auto">
|
||||
{selectedLog.exceptions}
|
||||
</pre>
|
||||
</AdaptableCard>
|
||||
)}
|
||||
|
||||
{/* Comments */}
|
||||
{selectedLog.comments && (
|
||||
<AdaptableCard className="lg:col-span-2 shadow-sm bg-blue-50">
|
||||
<h6 className="font-semibold text-blue-700 mb-2 flex items-center gap-2">
|
||||
<HiOutlineDocumentText className="w-5 h-5" />
|
||||
Comments
|
||||
</h6>
|
||||
<p className="text-sm text-blue-800">{selectedLog.comments}</p>
|
||||
</AdaptableCard>
|
||||
)}
|
||||
</div>
|
||||
</TabContent>
|
||||
|
||||
{/* ACTIONS */}
|
||||
{/* ACTIONS TAB */}
|
||||
<TabContent value="actions">
|
||||
{selectedLog.actions?.map((action, index) => (
|
||||
<AdaptableCard key={index} className="mb-4">
|
||||
<h6 className="font-medium text-slate-600 mb-3 text-sm">Action #{index + 1}</h6>
|
||||
<table className="w-full text-xs">
|
||||
<tbody className="[&>tr>*]:py-0.5 [&>tr>td:first-child]:font-semibold [&>tr>td:first-child]:w-40 [&>tr>td:last-child]:break-all">
|
||||
<tr>
|
||||
<td>Service</td>
|
||||
<td>{action.serviceName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Method</td>
|
||||
<td>{action.methodName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Time</td>
|
||||
<td>{action.executionTime}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Duration</td>
|
||||
<td>{action.executionDuration} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Parameters</td>
|
||||
<td>
|
||||
<pre className="bg-slate-100 p-2 rounded text-[11px] whitespace-pre-wrap leading-snug">
|
||||
{action.parameters}
|
||||
{!selectedLog.actions || selectedLog.actions.length === 0 ? (
|
||||
<div className="text-center py-16">
|
||||
<HiOutlineCode className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||
<p className="text-gray-500">No actions recorded</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{selectedLog.actions.map((action, index) => (
|
||||
<AdaptableCard
|
||||
key={index}
|
||||
className="shadow-sm hover:shadow-md transition-shadow"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<h6 className="font-semibold text-gray-700 flex items-center gap-2">
|
||||
<Badge className="bg-indigo-500" content={`#${index + 1}`}></Badge>
|
||||
<span className="text-sm">{action.methodName}</span>
|
||||
</h6>
|
||||
<Badge
|
||||
className="border border-gray-300 bg-white text-gray-700 text-xs"
|
||||
content={formatDuration(action.executionDuration)}
|
||||
></Badge>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 mb-1">Service Name</p>
|
||||
<p className="text-sm font-mono text-gray-700 break-all">
|
||||
{action.serviceName}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 mb-1">Execution Time</p>
|
||||
<p className="text-sm text-gray-700">
|
||||
{new Date(action.executionTime).toLocaleString('tr-TR')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{action.parameters && (
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 mb-2">Parameters</p>
|
||||
<pre className="bg-gray-50 border border-gray-200 p-3 rounded-lg text-xs overflow-x-auto">
|
||||
{JSON.stringify(JSON.parse(action.parameters), null, 2)}
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</AdaptableCard>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</AdaptableCard>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TabContent>
|
||||
|
||||
{/* ENTITY CHANGES */}
|
||||
{/* ENTITY CHANGES TAB */}
|
||||
<TabContent value="changes">
|
||||
{selectedLog.entityChanges?.map((change, index) => (
|
||||
<AdaptableCard key={index} className="mb-4">
|
||||
<h6 className="font-medium text-slate-600 mb-3 text-sm">Change #{index + 1}</h6>
|
||||
<table className="w-full text-xs">
|
||||
<tbody className="[&>tr>*]:py-0.5 [&>tr>td:first-child]:font-semibold [&>tr>td:first-child]:w-40 [&>tr>td:last-child]:break-all">
|
||||
<tr>
|
||||
<td>Time</td>
|
||||
<td>{new Date(change.changeTime).toLocaleString()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Type</td>
|
||||
<td>{change.changeType}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Entity ID</td>
|
||||
<td>{change.entityId}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Entity Type</td>
|
||||
<td>{change.entityTypeFullName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Property Changes</td>
|
||||
<td>
|
||||
<ul className="list-disc pl-4 space-y-1">
|
||||
{change.propertyChanges.map((p, i) => (
|
||||
<li key={i}>
|
||||
<code>{p.propertyName}</code> ({p.propertyTypeFullName}):{' '}
|
||||
{!p.originalValue || p.originalValue === 'null' ? (
|
||||
<span className="text-green-600 font-semibold ml-1">
|
||||
{p.newValue}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-gray-500">{p.originalValue}</span>{' '}
|
||||
<span className="mx-1">→</span>
|
||||
<span className="text-yellow-600 font-semibold">
|
||||
{p.newValue}
|
||||
{!selectedLog.entityChanges || selectedLog.entityChanges.length === 0 ? (
|
||||
<div className="text-center py-16">
|
||||
<HiOutlineCheckCircle className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||
<p className="text-gray-500">No entity changes recorded</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{selectedLog.entityChanges.map((change, index) => (
|
||||
<AdaptableCard
|
||||
key={index}
|
||||
className="shadow-sm hover:shadow-md transition-shadow"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge className="bg-purple-500" content={`#${index + 1}`}></Badge>
|
||||
<div>
|
||||
<h6 className="font-semibold text-gray-700">
|
||||
{change.entityTypeFullName}
|
||||
</h6>
|
||||
<p className="text-xs text-gray-500">ID: {change.entityId}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{getChangeTypeBadge(change.changeType)}
|
||||
<span className="text-xs text-gray-500">
|
||||
{new Date(change.changeTime).toLocaleTimeString('tr-TR')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{change.propertyChanges && change.propertyChanges.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs font-medium text-gray-600 mb-3">Property Changes</p>
|
||||
<div className="space-y-2">
|
||||
{change.propertyChanges.map((prop, i) => (
|
||||
<div key={i} className="bg-gray-50 p-3 rounded-lg">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<code className="text-sm font-semibold text-indigo-600">
|
||||
{prop.propertyName}
|
||||
</code>
|
||||
<span className="text-xs text-gray-500 ml-2">
|
||||
({prop.propertyTypeFullName?.split('.').pop()})
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
{!prop.originalValue ||
|
||||
prop.originalValue === 'null' ||
|
||||
prop.originalValue === '[Not Tracked]' ? (
|
||||
<span className="px-3 py-1 bg-green-100 text-green-700 rounded-md font-medium">
|
||||
{prop.newValue || 'null'}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="px-3 py-1 bg-gray-100 text-gray-600 rounded-md">
|
||||
{prop.originalValue}
|
||||
</span>
|
||||
<span className="text-gray-400">→</span>
|
||||
<span className="px-3 py-1 bg-yellow-100 text-yellow-700 rounded-md font-medium">
|
||||
{prop.newValue || 'null'}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</AdaptableCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</AdaptableCard>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in a new issue