access logs detail
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
DocumentText,
|
DocumentText,
|
||||||
Refresh,
|
|
||||||
Activity,
|
Activity,
|
||||||
InfoCircle
|
InfoCircle,
|
||||||
|
Refresh
|
||||||
} from "iconsax-react";
|
} from "iconsax-react";
|
||||||
import Button from "../../components/Button";
|
import Button from "../../components/Button";
|
||||||
import Pagination from "../../components/Pagination";
|
import Pagination from "../../components/Pagination";
|
||||||
@@ -14,7 +14,7 @@ import FilterPanel from "./components/FilterPanel";
|
|||||||
import AccessLogsTable from "./components/AccessLogsTable";
|
import AccessLogsTable from "./components/AccessLogsTable";
|
||||||
import LogDetailsModal from "./components/LogDetailsModal";
|
import LogDetailsModal from "./components/LogDetailsModal";
|
||||||
import { useAccessLogs, useAccessLogDetail } from "./hooks/useAccessLogs";
|
import { useAccessLogs, useAccessLogDetail } from "./hooks/useAccessLogs";
|
||||||
import { AccessLog } from "./types/AccessLogTypes";
|
import { AccessLog, OperationType, UserType } from "./types/AccessLogTypes";
|
||||||
|
|
||||||
const AccessLogsList: React.FC = () => {
|
const AccessLogsList: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
@@ -28,17 +28,32 @@ const AccessLogsList: React.FC = () => {
|
|||||||
refetch,
|
refetch,
|
||||||
} = useAccessLogs();
|
} = useAccessLogs();
|
||||||
|
|
||||||
const { log: selectedLog, fetchLog, clearLog } = useAccessLogDetail();
|
const { log: selectedLog, loading: detailLoading, error: detailError, fetchLog, clearLog } = useAccessLogDetail();
|
||||||
const [isDetailsModalOpen, setIsDetailsModalOpen] = useState(false);
|
const [isDetailsModalOpen, setIsDetailsModalOpen] = useState(false);
|
||||||
|
const [directLog, setDirectLog] = useState<AccessLog | null>(null);
|
||||||
|
|
||||||
const handleViewDetails = async (log: AccessLog) => {
|
const handleViewDetails = async (log: AccessLog) => {
|
||||||
await fetchLog(log.id);
|
try {
|
||||||
setIsDetailsModalOpen(true);
|
setIsDetailsModalOpen(true);
|
||||||
|
// اگر لاگ کامل است (مثل دادههای نمونه)، مستقیماً نمایش بده
|
||||||
|
if (log.user !== undefined || log.oldValues !== undefined || log.newValues !== undefined) {
|
||||||
|
setDirectLog(log);
|
||||||
|
clearLog(); // پاک کردن selectedLog
|
||||||
|
} else {
|
||||||
|
// اگر لاگ از API آمده، جزئیات کامل را دریافت کن
|
||||||
|
setDirectLog(null);
|
||||||
|
await fetchLog(log.id);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در دریافت جزئیات لاگ:', error);
|
||||||
|
// مودال باز میماند تا خطا نمایش داده شود
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseDetailsModal = () => {
|
const handleCloseDetailsModal = () => {
|
||||||
setIsDetailsModalOpen(false);
|
setIsDetailsModalOpen(false);
|
||||||
clearLog();
|
clearLog();
|
||||||
|
setDirectLog(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSort = (field: string, order: "ASC" | "DESC") => {
|
const handleSort = (field: string, order: "ASC" | "DESC") => {
|
||||||
@@ -80,6 +95,36 @@ const AccessLogsList: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
// تست مودال با دادههای نمونه
|
||||||
|
const sampleLog: AccessLog = {
|
||||||
|
id: "eb528348-d1c8-4216-8a78-fa62ca56b65a",
|
||||||
|
createdAt: "2025-09-02T12:55:47.593Z",
|
||||||
|
updatedAt: "2025-09-02T12:55:47.593Z",
|
||||||
|
operationType: "UPDATE" as OperationType,
|
||||||
|
userType: "ADMIN" as UserType,
|
||||||
|
entityName: "Blog",
|
||||||
|
entityId: "9763ceb4-ce32-4474-979c-5581c9d2cd24",
|
||||||
|
actionDescription: "Updated Blog",
|
||||||
|
oldValues: undefined,
|
||||||
|
newValues: '{"userId":"2c6895a4-e04a-4b1d-aa22-390c37353630","userType":"ADMIN","actionDescription":"Admin updated blog: میل سرور چیست؟ راهنمای کاربردی + معرفی بهترین گزینه برای شرکتها | DanakCorp","metadata":{"categoryId":"a940f9b5-c1a9-4ff1-b67d-ae78c62fc792"}}',
|
||||||
|
ipAddress: undefined,
|
||||||
|
userAgent: undefined,
|
||||||
|
endpoint: undefined,
|
||||||
|
statusCode: undefined,
|
||||||
|
errorMessage: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
requestId: undefined,
|
||||||
|
user: undefined
|
||||||
|
};
|
||||||
|
handleViewDetails(sampleLog);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2 border border-blue-300 bg-blue-50 text-blue-700 hover:bg-blue-100"
|
||||||
|
>
|
||||||
|
<InfoCircle size={16} />
|
||||||
|
تست مودال
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={refetch}
|
onClick={refetch}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
@@ -174,9 +219,11 @@ const AccessLogsList: React.FC = () => {
|
|||||||
|
|
||||||
{/* Details Modal */}
|
{/* Details Modal */}
|
||||||
<LogDetailsModal
|
<LogDetailsModal
|
||||||
log={selectedLog}
|
log={directLog || selectedLog}
|
||||||
isOpen={isDetailsModalOpen}
|
isOpen={isDetailsModalOpen}
|
||||||
onClose={handleCloseDetailsModal}
|
onClose={handleCloseDetailsModal}
|
||||||
|
loading={detailLoading}
|
||||||
|
error={detailError}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Empty State Info */}
|
{/* Empty State Info */}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Eye, ArrowUp, ArrowDown, Clock, User, DocumentText } from "iconsax-react";
|
import { ArrowUp, ArrowDown, Clock, User, DocumentText } from "iconsax-react";
|
||||||
import Button from "../../../components/Button";
|
import Button from "../../../components/Button";
|
||||||
import StatusCircle from "../../../components/StatusCircle";
|
import StatusCircle from "../../../components/StatusCircle";
|
||||||
import Td from "../../../components/Td";
|
import Td from "../../../components/Td";
|
||||||
@@ -224,9 +224,8 @@ const AccessLogsTable: React.FC<AccessLogsTableProps> = ({
|
|||||||
<Td text="">
|
<Td text="">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onViewDetails(log)}
|
onClick={() => onViewDetails(log)}
|
||||||
className="text-sm px-3 py-1 bg-transparent text-blue-600 hover:text-blue-800 hover:bg-blue-50"
|
className="w-fit px-4"
|
||||||
>
|
>
|
||||||
<Eye size={16} className="ml-1" />
|
|
||||||
جزئیات
|
جزئیات
|
||||||
</Button>
|
</Button>
|
||||||
</Td>
|
</Td>
|
||||||
|
|||||||
@@ -26,14 +26,18 @@ interface LogDetailsModalProps {
|
|||||||
log: AccessLog | null;
|
log: AccessLog | null;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
loading?: boolean;
|
||||||
|
error?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LogDetailsModal: React.FC<LogDetailsModalProps> = ({
|
const LogDetailsModal: React.FC<LogDetailsModalProps> = ({
|
||||||
log,
|
log,
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
|
loading = false,
|
||||||
|
error = null,
|
||||||
}) => {
|
}) => {
|
||||||
if (!log) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
const getOperationTypeColor = (type: OperationType): string => {
|
const getOperationTypeColor = (type: OperationType): string => {
|
||||||
const colors = {
|
const colors = {
|
||||||
@@ -154,209 +158,245 @@ const LogDetailsModal: React.FC<LogDetailsModalProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Basic Information */}
|
{/* Loading State */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
{loading && (
|
||||||
<InfoRow
|
<div className="flex items-center justify-center py-12">
|
||||||
icon={<Activity size={18} />}
|
<div className="text-center">
|
||||||
label="نوع عملیات"
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||||
value={
|
<p className="text-gray-600">در حال بارگذاری جزئیات...</p>
|
||||||
<StatusCircle
|
</div>
|
||||||
color={getOperationTypeColor(log.operationType)}
|
</div>
|
||||||
text={OperationTypeLabels[log.operationType]}
|
)}
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InfoRow
|
{/* Error State */}
|
||||||
icon={<User size={18} />}
|
{error && (
|
||||||
label="نوع کاربر"
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
|
||||||
value={
|
<div className="flex items-center gap-3">
|
||||||
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getUserTypeBadgeColor(log.userType)}`}>
|
<Warning2 size={20} className="text-red-600" />
|
||||||
{UserTypeLabels[log.userType]}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InfoRow
|
|
||||||
icon={<DocumentText size={18} />}
|
|
||||||
label="موجودیت"
|
|
||||||
value={
|
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">{log.entityName}</div>
|
<h3 className="text-sm font-medium text-red-900">خطا در بارگذاری</h3>
|
||||||
{log.entityId && (
|
<p className="text-sm text-red-700 mt-1">{error}</p>
|
||||||
<div className="text-xs text-gray-500 mt-1">{log.entityId}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
/>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<InfoRow
|
{/* Content - Only show if not loading and no error */}
|
||||||
icon={<Clock size={18} />}
|
{!loading && !error && log && (
|
||||||
label="تاریخ و زمان"
|
<>
|
||||||
value={formatDate(log.createdAt)}
|
{/* Basic Information */}
|
||||||
/>
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||||
|
<InfoRow
|
||||||
|
icon={<Activity size={18} />}
|
||||||
|
label="نوع عملیات"
|
||||||
|
value={
|
||||||
|
<StatusCircle
|
||||||
|
color={getOperationTypeColor(log.operationType)}
|
||||||
|
text={OperationTypeLabels[log.operationType]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
{log.actionDescription && (
|
|
||||||
<InfoRow
|
|
||||||
icon={<InfoCircle size={18} />}
|
|
||||||
label="توضیحات عملیات"
|
|
||||||
value={log.actionDescription}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* User Information */}
|
|
||||||
{log.user && (
|
|
||||||
<div className="mb-6">
|
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-3 flex items-center gap-2">
|
|
||||||
<User size={18} />
|
|
||||||
اطلاعات کاربر
|
|
||||||
</h3>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<InfoRow
|
<InfoRow
|
||||||
icon={<User size={18} />}
|
icon={<User size={18} />}
|
||||||
label="نام و نام خانوادگی"
|
label="نوع کاربر"
|
||||||
value={`${log.user.firstName} ${log.user.lastName}`}
|
value={
|
||||||
|
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getUserTypeBadgeColor(log.userType)}`}>
|
||||||
|
{UserTypeLabels[log.userType]}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<InfoRow
|
|
||||||
icon={<InfoCircle size={18} />}
|
|
||||||
label="ایمیل"
|
|
||||||
value={log.user.email}
|
|
||||||
/>
|
|
||||||
{log.user.phone && (
|
|
||||||
<InfoRow
|
|
||||||
icon={<InfoCircle size={18} />}
|
|
||||||
label="شماره تماس"
|
|
||||||
value={log.user.phone}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<InfoRow
|
|
||||||
icon={<InfoCircle size={18} />}
|
|
||||||
label="شناسه کاربر"
|
|
||||||
value={log.user.id}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Technical Details */}
|
|
||||||
<div className="mb-6">
|
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-3 flex items-center gap-2">
|
|
||||||
<Monitor size={18} />
|
|
||||||
جزئیات فنی
|
|
||||||
</h3>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
{log.ipAddress && (
|
|
||||||
<InfoRow
|
<InfoRow
|
||||||
icon={<Monitor size={18} />}
|
icon={<DocumentText size={18} />}
|
||||||
label="آدرس IP"
|
label="موجودیت"
|
||||||
value={log.ipAddress}
|
value={
|
||||||
|
<div>
|
||||||
|
<div className="font-medium">{log.entityName}</div>
|
||||||
|
{log.entityId && (
|
||||||
|
<div className="text-xs text-gray-500 mt-1">{log.entityId}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
{log.endpoint && (
|
|
||||||
<InfoRow
|
|
||||||
icon={<Code size={18} />}
|
|
||||||
label="Endpoint"
|
|
||||||
value={log.endpoint}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{log.httpMethod && (
|
|
||||||
<InfoRow
|
|
||||||
icon={<Code size={18} />}
|
|
||||||
label="متد HTTP"
|
|
||||||
value={HttpMethodLabels[log.httpMethod]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<InfoRow
|
|
||||||
icon={<InfoCircle size={18} />}
|
|
||||||
label="کد وضعیت"
|
|
||||||
value={
|
|
||||||
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getStatusColor(log.statusCode)}`}>
|
|
||||||
{log.statusCode || "N/A"}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{log.executionTime && (
|
|
||||||
<InfoRow
|
<InfoRow
|
||||||
icon={<Clock size={18} />}
|
icon={<Clock size={18} />}
|
||||||
label="زمان اجرا"
|
label="تاریخ و زمان"
|
||||||
value={formatExecutionTime(log.executionTime)}
|
value={formatDate(log.createdAt)}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
{log.sessionId && (
|
{log.actionDescription && (
|
||||||
<InfoRow
|
<InfoRow
|
||||||
icon={<InfoCircle size={18} />}
|
icon={<InfoCircle size={18} />}
|
||||||
label="شناسه جلسه"
|
label="توضیحات عملیات"
|
||||||
value={log.sessionId}
|
value={log.actionDescription}
|
||||||
/>
|
fullWidth
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
{log.requestId && (
|
|
||||||
<InfoRow
|
|
||||||
icon={<InfoCircle size={18} />}
|
|
||||||
label="شناسه درخواست"
|
|
||||||
value={log.requestId}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Error Information */}
|
|
||||||
{log.errorMessage && (
|
|
||||||
<div className="mb-6">
|
|
||||||
<h3 className="text-lg font-semibold text-red-600 mb-3 flex items-center gap-2">
|
|
||||||
<Warning2 size={18} />
|
|
||||||
اطلاعات خطا
|
|
||||||
</h3>
|
|
||||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
|
||||||
<p className="text-red-800">{log.errorMessage}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* User Information */}
|
||||||
|
{log.user && (
|
||||||
|
<div className="mb-6">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 mb-3 flex items-center gap-2">
|
||||||
|
<User size={18} />
|
||||||
|
اطلاعات کاربر
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<InfoRow
|
||||||
|
icon={<User size={18} />}
|
||||||
|
label="نام و نام خانوادگی"
|
||||||
|
value={`${log.user.firstName} ${log.user.lastName}`}
|
||||||
|
/>
|
||||||
|
<InfoRow
|
||||||
|
icon={<InfoCircle size={18} />}
|
||||||
|
label="ایمیل"
|
||||||
|
value={log.user.email}
|
||||||
|
/>
|
||||||
|
{log.user.phone && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<InfoCircle size={18} />}
|
||||||
|
label="شماره تماس"
|
||||||
|
value={log.user.phone}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<InfoRow
|
||||||
|
icon={<InfoCircle size={18} />}
|
||||||
|
label="شناسه کاربر"
|
||||||
|
value={log.user.id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Technical Details */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 mb-3 flex items-center gap-2">
|
||||||
|
<Monitor size={18} />
|
||||||
|
جزئیات فنی
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{log.ipAddress && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<Monitor size={18} />}
|
||||||
|
label="آدرس IP"
|
||||||
|
value={log.ipAddress}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{log.endpoint && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<Code size={18} />}
|
||||||
|
label="Endpoint"
|
||||||
|
value={log.endpoint}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{log.httpMethod && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<Code size={18} />}
|
||||||
|
label="متد HTTP"
|
||||||
|
value={HttpMethodLabels[log.httpMethod]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<InfoRow
|
||||||
|
icon={<InfoCircle size={18} />}
|
||||||
|
label="کد وضعیت"
|
||||||
|
value={
|
||||||
|
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getStatusColor(log.statusCode)}`}>
|
||||||
|
{log.statusCode || "N/A"}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{log.executionTime && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<Clock size={18} />}
|
||||||
|
label="زمان اجرا"
|
||||||
|
value={formatExecutionTime(log.executionTime)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{log.sessionId && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<InfoCircle size={18} />}
|
||||||
|
label="شناسه جلسه"
|
||||||
|
value={log.sessionId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{log.requestId && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<InfoCircle size={18} />}
|
||||||
|
label="شناسه درخواست"
|
||||||
|
value={log.requestId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Error Information */}
|
||||||
|
{log.errorMessage && (
|
||||||
|
<div className="mb-6">
|
||||||
|
<h3 className="text-lg font-semibold text-red-600 mb-3 flex items-center gap-2">
|
||||||
|
<Warning2 size={18} />
|
||||||
|
اطلاعات خطا
|
||||||
|
</h3>
|
||||||
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||||
|
<p className="text-red-800">{log.errorMessage}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* User Agent */}
|
||||||
|
{log.userAgent && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<Monitor size={18} />}
|
||||||
|
label="User Agent"
|
||||||
|
value={
|
||||||
|
<div className="text-xs font-mono bg-gray-100 p-2 rounded break-all">
|
||||||
|
{log.userAgent}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Data Changes */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<CodeBlock title="مقادیر قبلی" content={log.oldValues} />
|
||||||
|
<CodeBlock title="مقادیر جدید" content={log.newValues} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Metadata */}
|
||||||
|
{log.metadata && Object.keys(log.metadata).length > 0 && (
|
||||||
|
<CodeBlock
|
||||||
|
title="متادیتا"
|
||||||
|
content={JSON.stringify(log.metadata, null, 2)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div className="flex justify-end mt-8 pt-6 border-t border-gray-200">
|
||||||
|
<Button
|
||||||
|
onClick={onClose}
|
||||||
|
className="bg-blue-600 text-white hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* No Data State */}
|
||||||
|
{!loading && !error && !log && (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<InfoCircle size={48} className="text-gray-400 mx-auto mb-4" />
|
||||||
|
<p className="text-gray-600">هیچ دادهای برای نمایش وجود ندارد</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* User Agent */}
|
|
||||||
{log.userAgent && (
|
|
||||||
<InfoRow
|
|
||||||
icon={<Monitor size={18} />}
|
|
||||||
label="User Agent"
|
|
||||||
value={
|
|
||||||
<div className="text-xs font-mono bg-gray-100 p-2 rounded break-all">
|
|
||||||
{log.userAgent}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Data Changes */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
<CodeBlock title="مقادیر قبلی" content={log.oldValues} />
|
|
||||||
<CodeBlock title="مقادیر جدید" content={log.newValues} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Metadata */}
|
|
||||||
{log.metadata && Object.keys(log.metadata).length > 0 && (
|
|
||||||
<CodeBlock
|
|
||||||
title="متادیتا"
|
|
||||||
content={JSON.stringify(log.metadata, null, 2)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<div className="flex justify-end mt-8 pt-6 border-t border-gray-200">
|
|
||||||
<Button
|
|
||||||
onClick={onClose}
|
|
||||||
className="bg-blue-600 text-white hover:bg-blue-700"
|
|
||||||
>
|
|
||||||
بستن
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</DefaulModal>
|
</DefaulModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -79,6 +79,14 @@ class AccessLogsService {
|
|||||||
// Get specific log by ID
|
// Get specific log by ID
|
||||||
async getLogById(id: string): Promise<{ accessLog: AccessLog }> {
|
async getLogById(id: string): Promise<{ accessLog: AccessLog }> {
|
||||||
const response = await axios.get(`${this.baseUrl}/${id}`);
|
const response = await axios.get(`${this.baseUrl}/${id}`);
|
||||||
|
|
||||||
|
// Handle the nested data structure
|
||||||
|
if (response.data.data && response.data.data.accessLog) {
|
||||||
|
return {
|
||||||
|
accessLog: response.data.data.accessLog,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user