add debug to system managment
This commit is contained in:
parent
11caf42da8
commit
82d39dbb42
|
@ -21,6 +21,9 @@ framework:
|
|||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
http_client:
|
||||
default_options:
|
||||
timeout: 30
|
||||
php_errors:
|
||||
log: true
|
||||
|
||||
|
|
|
@ -40,6 +40,10 @@ services:
|
|||
- '../src/Entity/'
|
||||
- '../src/Kernel.php'
|
||||
|
||||
App\Controller\System\DebugController:
|
||||
arguments:
|
||||
$kernelLogsDir: '%kernel.logs_dir%'
|
||||
|
||||
doctrine.orm.default_attribute_driver:
|
||||
class: Doctrine\ORM\Mapping\Driver\AttributeDriver
|
||||
arguments:
|
||||
|
@ -122,7 +126,37 @@ services:
|
|||
arguments:
|
||||
$entityManager: '@doctrine.orm.entity_manager'
|
||||
|
||||
App\Cog\TicketService:
|
||||
arguments:
|
||||
$entityManager: '@doctrine.orm.entity_manager'
|
||||
$explore: '@App\Service\Explore'
|
||||
$jdate: '@Jdate'
|
||||
$registryMGR: '@registryMGR'
|
||||
$sms: '@SMS'
|
||||
$uploadDirectory: '%SupportFilesDir%'
|
||||
|
||||
App\Service\Explore: ~
|
||||
|
||||
App\AiTool\AccountingDocService:
|
||||
arguments:
|
||||
$em: '@doctrine.orm.entity_manager'
|
||||
$cogAccountingDocService: '@App\Cog\AccountingDocService'
|
||||
|
||||
App\AiTool\TicketService:
|
||||
arguments:
|
||||
$em: '@doctrine.orm.entity_manager'
|
||||
$cogTicketService: '@App\Cog\TicketService'
|
||||
|
||||
App\Service\AGI\AGIService:
|
||||
arguments:
|
||||
$entityManager: '@doctrine.orm.entity_manager'
|
||||
$registryMGR: '@registryMGR'
|
||||
$log: '@Log'
|
||||
$provider: '@Provider'
|
||||
$promptService: '@App\Service\AGI\Promps\PromptService'
|
||||
$httpClient: '@http_client'
|
||||
$httpKernel: '@kernel'
|
||||
$explore: '@App\Service\Explore'
|
||||
$jdate: '@Jdate'
|
||||
$sms: '@SMS'
|
||||
$uploadDirectory: '%SupportFilesDir%'
|
||||
|
|
|
@ -3,13 +3,115 @@
|
|||
namespace App\AiTool;
|
||||
|
||||
use App\Cog\TicketService as CogTicketService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
class TicketService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CogTicketService $cogTicketService
|
||||
) {
|
||||
private EntityManagerInterface $em;
|
||||
private CogTicketService $cogTicketService;
|
||||
|
||||
public function __construct(EntityManagerInterface $em, CogTicketService $cogTicketService)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->cogTicketService = $cogTicketService;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت لیست تیکتها برای ابزار هوش مصنوعی
|
||||
*/
|
||||
public function getTicketsListAi(array $params, $acc = null): array
|
||||
{
|
||||
$acc = $acc ?? ($params['acc'] ?? null);
|
||||
if (!$acc) {
|
||||
return [
|
||||
'error' => 'اطلاعات دسترسی (acc) الزامی است'
|
||||
];
|
||||
}
|
||||
try {
|
||||
// اینجا باید منطق دریافت لیست تیکتها پیادهسازی شود
|
||||
// فعلاً یک پیام موقت برمیگردانیم
|
||||
return [
|
||||
'error' => 'این قابلیت در حال توسعه است'
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'error' => 'خطا در دریافت لیست تیکتها: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت اطلاعات تیکت بر اساس کد
|
||||
*/
|
||||
public function getTicketInfoByCode($code, $acc): array
|
||||
{
|
||||
if (!$code) {
|
||||
return [
|
||||
'error' => 'کد تیکت الزامی است'
|
||||
];
|
||||
}
|
||||
if (!$acc) {
|
||||
return [
|
||||
'error' => 'اطلاعات دسترسی (acc) الزامی است'
|
||||
];
|
||||
}
|
||||
try {
|
||||
// اینجا باید منطق دریافت اطلاعات تیکت پیادهسازی شود
|
||||
return [
|
||||
'error' => 'این قابلیت در حال توسعه است'
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'error' => 'خطا در دریافت اطلاعات تیکت: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* افزودن یا ویرایش تیکت برای ابزار هوش مصنوعی
|
||||
*/
|
||||
public function addOrUpdateTicketAi(array $params, $acc = null, $code = 0): array
|
||||
{
|
||||
$acc = $acc ?? ($params['acc'] ?? null);
|
||||
if (!$acc) {
|
||||
return [
|
||||
'error' => 'اطلاعات دسترسی (acc) الزامی است'
|
||||
];
|
||||
}
|
||||
try {
|
||||
// اینجا باید منطق افزودن/ویرایش تیکت پیادهسازی شود
|
||||
return [
|
||||
'error' => 'این قابلیت در حال توسعه است'
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'error' => 'خطا در افزودن/ویرایش تیکت: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* پاسخ به تیکت برای ابزار هوش مصنوعی
|
||||
*/
|
||||
public function replyToTicketAi(array $params, $acc = null): array
|
||||
{
|
||||
$acc = $acc ?? ($params['acc'] ?? null);
|
||||
if (!$acc) {
|
||||
return [
|
||||
'error' => 'اطلاعات دسترسی (acc) الزامی است'
|
||||
];
|
||||
}
|
||||
try {
|
||||
// اینجا باید منطق پاسخ به تیکت پیادهسازی شود
|
||||
return [
|
||||
'error' => 'این قابلیت در حال توسعه است'
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'error' => 'خطا در پاسخ به تیکت: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,6 +14,7 @@ use App\Service\Notification;
|
|||
use App\Service\Provider;
|
||||
use App\Service\registryMGR;
|
||||
use App\Service\SMS;
|
||||
use App\AiTool\TicketService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
|
684
hesabixCore/src/Controller/System/DebugController.php
Normal file
684
hesabixCore/src/Controller/System/DebugController.php
Normal file
|
@ -0,0 +1,684 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller\System;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
#[Route('/api/admin/debug')]
|
||||
class DebugController extends AbstractController
|
||||
{
|
||||
private string $logsDir;
|
||||
private Filesystem $filesystem;
|
||||
private string $environment;
|
||||
|
||||
public function __construct(string $kernelLogsDir, KernelInterface $kernel)
|
||||
{
|
||||
$this->logsDir = $kernelLogsDir;
|
||||
$this->filesystem = new Filesystem();
|
||||
$this->environment = $kernel->getEnvironment();
|
||||
}
|
||||
|
||||
#[Route('/logs', name: 'debug_logs_list', methods: ['GET'])]
|
||||
public function getLogs(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$page = (int) $request->query->get('page', 1);
|
||||
$limit = (int) $request->query->get('limit', 50);
|
||||
$search = (string) $request->query->get('search', '');
|
||||
$level = (string) $request->query->get('level', '');
|
||||
$date = (string) $request->query->get('date', '');
|
||||
|
||||
// Handle sorting parameters safely
|
||||
$sortBy = 'timestamp';
|
||||
$sortDesc = true;
|
||||
|
||||
// Get sortBy parameter safely
|
||||
$sortByParam = $request->query->get('sortBy');
|
||||
if (is_string($sortByParam) && !empty($sortByParam)) {
|
||||
$sortBy = $sortByParam;
|
||||
}
|
||||
|
||||
// Get sortDesc parameter safely
|
||||
$sortDescParam = $request->query->get('sortDesc');
|
||||
if (is_string($sortDescParam)) {
|
||||
$sortDesc = $sortDescParam === 'true' || $sortDescParam === '1';
|
||||
} elseif (is_bool($sortDescParam)) {
|
||||
$sortDesc = $sortDescParam;
|
||||
}
|
||||
|
||||
// محدود کردن تعداد آیتمها برای جلوگیری از مصرف حافظه زیاد
|
||||
$limit = min($limit, 100);
|
||||
|
||||
$logs = $this->parseLogFilesOptimized($page, $limit, $search, $level, $date, $sortBy, $sortDesc);
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'data' => $logs['items'],
|
||||
'total' => $logs['total'],
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'totalPages' => ceil($logs['total'] / $limit),
|
||||
'environment' => $this->environment
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'خطا در دریافت لاگها: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/logs/{id}', name: 'debug_log_detail', methods: ['GET'])]
|
||||
public function getLogDetail(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$logDetail = $this->getLogDetailById($id);
|
||||
|
||||
if (!$logDetail) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'لاگ مورد نظر یافت نشد'
|
||||
], 404);
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'data' => $logDetail
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'خطا در دریافت جزئیات لاگ: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/logs', name: 'debug_logs_delete', methods: ['DELETE'])]
|
||||
public function deleteLogs(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$data = json_decode($request->getContent(), true);
|
||||
$logIds = $data['ids'] ?? [];
|
||||
$deleteAll = $data['deleteAll'] ?? false;
|
||||
|
||||
if ($deleteAll) {
|
||||
$this->clearAllLogs();
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'message' => 'تمام لاگها با موفقیت حذف شدند'
|
||||
]);
|
||||
}
|
||||
|
||||
if (empty($logIds)) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'هیچ لاگی برای حذف انتخاب نشده'
|
||||
], 400);
|
||||
}
|
||||
|
||||
$deletedCount = $this->deleteLogsByIds($logIds);
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'message' => "{$deletedCount} لاگ با موفقیت حذف شد",
|
||||
'deletedCount' => $deletedCount
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'خطا در حذف لاگها: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/logs/export', name: 'debug_logs_export', methods: ['GET'])]
|
||||
public function exportLogs(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$format = $request->query->get('format', 'json');
|
||||
$date = $request->query->get('date', '');
|
||||
$level = $request->query->get('level', '');
|
||||
|
||||
$logs = $this->getLogsForExport($date, $level);
|
||||
|
||||
if ($format === 'csv') {
|
||||
$csvData = $this->convertToCsv($logs);
|
||||
return new JsonResponse($csvData, 200, [
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition' => 'attachment; filename="logs_' . $this->environment . '_' . date('Y-m-d') . '.csv"'
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'data' => $logs,
|
||||
'total' => count($logs),
|
||||
'environment' => $this->environment
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'خطا در صادرات لاگها: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/system-info', name: 'debug_system_info', methods: ['GET'])]
|
||||
public function getSystemInfo(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$info = [
|
||||
'environment' => $this->environment,
|
||||
'php_version' => PHP_VERSION,
|
||||
'symfony_version' => \Symfony\Component\HttpKernel\Kernel::VERSION,
|
||||
'memory_usage' => memory_get_usage(true),
|
||||
'memory_peak' => memory_get_peak_usage(true),
|
||||
'disk_free_space' => disk_free_space($this->logsDir),
|
||||
'disk_total_space' => disk_total_space($this->logsDir),
|
||||
'log_files_count' => $this->getLogFilesCount(),
|
||||
'log_files_size' => $this->getLogFilesSize(),
|
||||
'last_error_log' => $this->getLastErrorLog(),
|
||||
'server_info' => [
|
||||
'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown',
|
||||
'php_sapi' => php_sapi_name(),
|
||||
'max_execution_time' => ini_get('max_execution_time'),
|
||||
'memory_limit' => ini_get('memory_limit'),
|
||||
]
|
||||
];
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'data' => $info
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'خطا در دریافت اطلاعات سیستم: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function parseLogFilesOptimized(int $page, int $limit, string $search, string $level, string $date, string $sortBy = 'timestamp', bool $sortDesc = true): array
|
||||
{
|
||||
$finder = new Finder();
|
||||
|
||||
// فقط فایلهای لاگ مربوط به محیط فعلی را پیدا کن
|
||||
$finder->files()
|
||||
->in($this->logsDir)
|
||||
->name('*.log')
|
||||
->filter(function ($file) {
|
||||
// فایلهای مربوط به محیط فعلی
|
||||
$filename = $file->getFilename();
|
||||
return strpos($filename, $this->environment . '.log') !== false ||
|
||||
strpos($filename, $this->environment) !== false ||
|
||||
$filename === 'dev.log' || $filename === 'prod.log' ||
|
||||
$filename === 'test.log';
|
||||
})
|
||||
->sortByModifiedTime();
|
||||
|
||||
$allLogs = [];
|
||||
$id = 1;
|
||||
$maxLogs = 5000; // محدود کردن تعداد کل لاگها برای جلوگیری از مصرف حافظه
|
||||
|
||||
foreach ($finder as $file) {
|
||||
// بررسی اندازه فایل قبل از خواندن
|
||||
if ($file->getSize() > 50 * 1024 * 1024) { // فایلهای بزرگتر از 50MB را رد کن
|
||||
continue;
|
||||
}
|
||||
|
||||
$handle = fopen($file->getPathname(), 'r');
|
||||
if (!$handle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lineNumber = 0;
|
||||
while (($line = fgets($handle)) !== false && count($allLogs) < $maxLogs) {
|
||||
$lineNumber++;
|
||||
|
||||
// محدود کردن تعداد خطوط خوانده شده
|
||||
if ($lineNumber > 10000) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty(trim($line))) continue;
|
||||
|
||||
$logEntry = $this->parseLogLine($line, $file->getFilename(), $id++);
|
||||
|
||||
if ($logEntry) {
|
||||
// فیلتر بر اساس جستجو
|
||||
if ($search && !$this->matchesSearch($logEntry, $search)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// فیلتر بر اساس سطح
|
||||
if ($level && $logEntry['level'] !== $level) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// فیلتر بر اساس تاریخ
|
||||
if ($date && $logEntry['date'] !== $date) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$allLogs[] = $logEntry;
|
||||
}
|
||||
|
||||
// بررسی مصرف حافظه
|
||||
if (memory_get_usage() > 100 * 1024 * 1024) { // بیش از 100MB
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
// اعمال مرتبسازی
|
||||
usort($allLogs, function($a, $b) use ($sortBy, $sortDesc) {
|
||||
$aValue = $a[$sortBy] ?? '';
|
||||
$bValue = $b[$sortBy] ?? '';
|
||||
|
||||
// برای تاریخ و زمان، از timestamp استفاده کن
|
||||
if ($sortBy === 'timestamp') {
|
||||
$aValue = strtotime($aValue);
|
||||
$bValue = strtotime($bValue);
|
||||
}
|
||||
|
||||
if ($sortDesc) {
|
||||
return $aValue < $bValue ? 1 : -1;
|
||||
} else {
|
||||
return $aValue > $bValue ? 1 : -1;
|
||||
}
|
||||
});
|
||||
|
||||
$totalCount = count($allLogs);
|
||||
$offset = ($page - 1) * $limit;
|
||||
$items = array_slice($allLogs, $offset, $limit);
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'total' => $totalCount
|
||||
];
|
||||
}
|
||||
|
||||
private function parseLogLine(string $line, string $filename, int $id): ?array
|
||||
{
|
||||
// فرمت لاگ Symfony: [timestamp] level: message {"context"} []
|
||||
$pattern = '/^\[([^\]]+)\]\s+([^:]+):\s+(.+?)(?:\s+\{([^}]*)\}\s+\[\])?$/';
|
||||
|
||||
if (preg_match($pattern, $line, $matches)) {
|
||||
$timestamp = $matches[1];
|
||||
$level = strtoupper(trim($matches[2]));
|
||||
$message = trim($matches[3]);
|
||||
$context = isset($matches[4]) ? $matches[4] : '';
|
||||
|
||||
// محدود کردن طول پیام
|
||||
if (strlen($message) > 1000) {
|
||||
$message = substr($message, 0, 1000) . '...';
|
||||
}
|
||||
|
||||
// پردازش context اگر وجود داشته باشد
|
||||
$extra = [];
|
||||
if (!empty($context)) {
|
||||
// تلاش برای پارس کردن context به عنوان JSON
|
||||
$contextData = json_decode('{' . $context . '}', true);
|
||||
if ($contextData) {
|
||||
$extra = $contextData;
|
||||
} else {
|
||||
$extra = ['context' => $context];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $id,
|
||||
'timestamp' => $timestamp,
|
||||
'date' => date('Y-m-d', strtotime($timestamp)),
|
||||
'time' => date('H:i:s', strtotime($timestamp)),
|
||||
'level' => $level,
|
||||
'message' => $message,
|
||||
'filename' => $filename,
|
||||
'environment' => $this->environment,
|
||||
'extra' => $extra,
|
||||
'raw' => substr($line, 0, 500)
|
||||
];
|
||||
}
|
||||
|
||||
// اگر فرمت استاندارد تطبیق نکرد، تلاش برای فرمتهای دیگر
|
||||
$patterns = [
|
||||
// فرمت JSON
|
||||
'/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)\s+(\w+)\s+(.+)$/',
|
||||
// فرمت استاندارد
|
||||
'/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+): (.+)$/',
|
||||
// فرمت ساده
|
||||
'/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+(\w+)\s+(.+)$/'
|
||||
];
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match($pattern, $line, $matches)) {
|
||||
$timestamp = $matches[1];
|
||||
$level = strtoupper($matches[2]);
|
||||
$message = $matches[3];
|
||||
|
||||
// محدود کردن طول پیام
|
||||
if (strlen($message) > 1000) {
|
||||
$message = substr($message, 0, 1000) . '...';
|
||||
}
|
||||
|
||||
// استخراج اطلاعات اضافی از JSON
|
||||
$extra = [];
|
||||
if (strpos($message, '{') === 0) {
|
||||
$jsonData = json_decode($message, true);
|
||||
if ($jsonData) {
|
||||
$message = $jsonData['message'] ?? $message;
|
||||
$extra = $jsonData;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $id,
|
||||
'timestamp' => $timestamp,
|
||||
'date' => date('Y-m-d', strtotime($timestamp)),
|
||||
'time' => date('H:i:s', strtotime($timestamp)),
|
||||
'level' => $level,
|
||||
'message' => $message,
|
||||
'filename' => $filename,
|
||||
'environment' => $this->environment,
|
||||
'extra' => $extra,
|
||||
'raw' => substr($line, 0, 500)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// اگر هیچ الگویی تطبیق نکرد، لاگ را با اطلاعات حداقلی برگردان
|
||||
return [
|
||||
'id' => $id,
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'date' => date('Y-m-d'),
|
||||
'time' => date('H:i:s'),
|
||||
'level' => 'UNKNOWN',
|
||||
'message' => substr($line, 0, 500),
|
||||
'filename' => $filename,
|
||||
'environment' => $this->environment,
|
||||
'extra' => [],
|
||||
'raw' => substr($line, 0, 500)
|
||||
];
|
||||
}
|
||||
|
||||
private function matchesSearch(array $logEntry, string $search): bool
|
||||
{
|
||||
$search = strtolower($search);
|
||||
return strpos(strtolower($logEntry['message']), $search) !== false ||
|
||||
strpos(strtolower($logEntry['level']), $search) !== false ||
|
||||
strpos(strtolower($logEntry['filename']), $search) !== false;
|
||||
}
|
||||
|
||||
private function getLogDetailById(string $id): ?array
|
||||
{
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->in($this->logsDir)
|
||||
->name('*.log')
|
||||
->filter(function ($file) {
|
||||
$filename = $file->getFilename();
|
||||
return strpos($filename, $this->environment . '.log') !== false ||
|
||||
strpos($filename, $this->environment) !== false ||
|
||||
$filename === 'dev.log' || $filename === 'prod.log' ||
|
||||
$filename === 'test.log';
|
||||
});
|
||||
|
||||
foreach ($finder as $file) {
|
||||
if ($file->getSize() > 10 * 1024 * 1024) { // فایلهای بزرگتر از 10MB را رد کن
|
||||
continue;
|
||||
}
|
||||
|
||||
$handle = fopen($file->getPathname(), 'r');
|
||||
if (!$handle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lineId = 1;
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
if (empty(trim($line))) {
|
||||
$lineId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($lineId == $id) {
|
||||
fclose($handle);
|
||||
return $this->parseLogLine($line, $file->getFilename(), $lineId);
|
||||
}
|
||||
$lineId++;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function deleteLogsByIds(array $ids): int
|
||||
{
|
||||
$deletedCount = 0;
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->in($this->logsDir)
|
||||
->name('*.log')
|
||||
->filter(function ($file) {
|
||||
$filename = $file->getFilename();
|
||||
return strpos($filename, $this->environment . '.log') !== false ||
|
||||
strpos($filename, $this->environment) !== false ||
|
||||
$filename === 'dev.log' || $filename === 'prod.log' ||
|
||||
$filename === 'test.log';
|
||||
});
|
||||
|
||||
foreach ($finder as $file) {
|
||||
if ($file->getSize() > 50 * 1024 * 1024) { // فایلهای بزرگتر از 50MB را رد کن
|
||||
continue;
|
||||
}
|
||||
|
||||
$handle = fopen($file->getPathname(), 'r');
|
||||
if (!$handle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
$lineId = 1;
|
||||
$fileModified = false;
|
||||
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
if (!in_array($lineId, $ids)) {
|
||||
$lines[] = $line;
|
||||
} else {
|
||||
$deletedCount++;
|
||||
$fileModified = true;
|
||||
}
|
||||
$lineId++;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
if ($fileModified) {
|
||||
$this->filesystem->dumpFile($file->getPathname(), implode('', $lines));
|
||||
}
|
||||
}
|
||||
|
||||
return $deletedCount;
|
||||
}
|
||||
|
||||
private function clearAllLogs(): void
|
||||
{
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->in($this->logsDir)
|
||||
->name('*.log')
|
||||
->filter(function ($file) {
|
||||
$filename = $file->getFilename();
|
||||
return strpos($filename, $this->environment . '.log') !== false ||
|
||||
strpos($filename, $this->environment) !== false ||
|
||||
$filename === 'dev.log' || $filename === 'prod.log' ||
|
||||
$filename === 'test.log';
|
||||
});
|
||||
|
||||
foreach ($finder as $file) {
|
||||
$this->filesystem->remove($file->getPathname());
|
||||
}
|
||||
}
|
||||
|
||||
private function getLogsForExport(string $date, string $level): array
|
||||
{
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->in($this->logsDir)
|
||||
->name('*.log')
|
||||
->filter(function ($file) {
|
||||
$filename = $file->getFilename();
|
||||
return strpos($filename, $this->environment . '.log') !== false ||
|
||||
strpos($filename, $this->environment) !== false ||
|
||||
$filename === 'dev.log' || $filename === 'prod.log' ||
|
||||
$filename === 'test.log';
|
||||
});
|
||||
|
||||
$logs = [];
|
||||
$id = 1;
|
||||
$maxLogs = 1000; // محدود کردن تعداد لاگهای صادر شده
|
||||
|
||||
foreach ($finder as $file) {
|
||||
if ($file->getSize() > 10 * 1024 * 1024) { // فایلهای بزرگتر از 10MB را رد کن
|
||||
continue;
|
||||
}
|
||||
|
||||
$handle = fopen($file->getPathname(), 'r');
|
||||
if (!$handle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
while (($line = fgets($handle)) !== false && count($logs) < $maxLogs) {
|
||||
if (empty(trim($line))) continue;
|
||||
|
||||
$logEntry = $this->parseLogLine($line, $file->getFilename(), $id++);
|
||||
|
||||
if ($logEntry) {
|
||||
if ($date && $logEntry['date'] !== $date) continue;
|
||||
if ($level && $logEntry['level'] !== $level) continue;
|
||||
|
||||
$logs[] = $logEntry;
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
return $logs;
|
||||
}
|
||||
|
||||
private function convertToCsv(array $logs): string
|
||||
{
|
||||
$csv = "ID,Date,Time,Level,Message,Filename,Environment\n";
|
||||
|
||||
foreach ($logs as $log) {
|
||||
$csv .= sprintf(
|
||||
"%d,%s,%s,%s,%s,%s,%s\n",
|
||||
$log['id'],
|
||||
$log['date'],
|
||||
$log['time'],
|
||||
$log['level'],
|
||||
str_replace(',', ';', $log['message']),
|
||||
$log['filename'],
|
||||
$log['environment']
|
||||
);
|
||||
}
|
||||
|
||||
return $csv;
|
||||
}
|
||||
|
||||
private function getLogFilesCount(): int
|
||||
{
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->in($this->logsDir)
|
||||
->name('*.log')
|
||||
->filter(function ($file) {
|
||||
$filename = $file->getFilename();
|
||||
return strpos($filename, $this->environment . '.log') !== false ||
|
||||
strpos($filename, $this->environment) !== false ||
|
||||
$filename === 'dev.log' || $filename === 'prod.log' ||
|
||||
$filename === 'test.log';
|
||||
});
|
||||
return iterator_count($finder);
|
||||
}
|
||||
|
||||
private function getLogFilesSize(): int
|
||||
{
|
||||
$size = 0;
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->in($this->logsDir)
|
||||
->name('*.log')
|
||||
->filter(function ($file) {
|
||||
$filename = $file->getFilename();
|
||||
return strpos($filename, $this->environment . '.log') !== false ||
|
||||
strpos($filename, $this->environment) !== false ||
|
||||
$filename === 'dev.log' || $filename === 'prod.log' ||
|
||||
$filename === 'test.log';
|
||||
});
|
||||
|
||||
foreach ($finder as $file) {
|
||||
$size += $file->getSize();
|
||||
}
|
||||
|
||||
return $size;
|
||||
}
|
||||
|
||||
private function getLastErrorLog(): ?array
|
||||
{
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->in($this->logsDir)
|
||||
->name('*.log')
|
||||
->filter(function ($file) {
|
||||
$filename = $file->getFilename();
|
||||
return strpos($filename, $this->environment . '.log') !== false ||
|
||||
strpos($filename, $this->environment) !== false ||
|
||||
$filename === 'dev.log' || $filename === 'prod.log' ||
|
||||
$filename === 'test.log';
|
||||
})
|
||||
->sortByModifiedTime();
|
||||
|
||||
$lastError = null;
|
||||
$lastTimestamp = 0;
|
||||
|
||||
foreach ($finder as $file) {
|
||||
if ($file->getSize() > 5 * 1024 * 1024) { // فایلهای بزرگتر از 5MB را رد کن
|
||||
continue;
|
||||
}
|
||||
|
||||
$handle = fopen($file->getPathname(), 'r');
|
||||
if (!$handle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
if (empty(trim($line))) continue;
|
||||
|
||||
$logEntry = $this->parseLogLine($line, $file->getFilename(), 1);
|
||||
|
||||
if ($logEntry && in_array($logEntry['level'], ['ERROR', 'CRITICAL', 'ALERT', 'EMERGENCY'])) {
|
||||
$timestamp = strtotime($logEntry['timestamp']);
|
||||
if ($timestamp > $lastTimestamp) {
|
||||
$lastTimestamp = $timestamp;
|
||||
$lastError = $logEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
return $lastError;
|
||||
}
|
||||
}
|
|
@ -9,6 +9,9 @@ use App\Service\registryMGR;
|
|||
use App\Service\Log;
|
||||
use App\Service\Provider;
|
||||
use App\Service\AGI\Promps\PromptService;
|
||||
use App\Service\Explore;
|
||||
use App\Service\Jdate;
|
||||
use App\Service\SMS;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
@ -23,6 +26,10 @@ class AGIService
|
|||
private $promptService;
|
||||
private $httpClient;
|
||||
private $httpKernel;
|
||||
private $explore;
|
||||
private $jdate;
|
||||
private $sms;
|
||||
private $uploadDirectory;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $entityManager,
|
||||
|
@ -31,7 +38,11 @@ class AGIService
|
|||
Provider $provider,
|
||||
PromptService $promptService,
|
||||
HttpClientInterface $httpClient,
|
||||
HttpKernelInterface $httpKernel
|
||||
HttpKernelInterface $httpKernel,
|
||||
Explore $explore,
|
||||
Jdate $jdate,
|
||||
SMS $sms,
|
||||
string $uploadDirectory
|
||||
) {
|
||||
$this->em = $entityManager;
|
||||
$this->registryMGR = $registryMGR;
|
||||
|
@ -40,6 +51,10 @@ class AGIService
|
|||
$this->promptService = $promptService;
|
||||
$this->httpClient = $httpClient;
|
||||
$this->httpKernel = $httpKernel;
|
||||
$this->explore = $explore;
|
||||
$this->jdate = $jdate;
|
||||
$this->sms = $sms;
|
||||
$this->uploadDirectory = $uploadDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -337,19 +352,19 @@ class AGIService
|
|||
return $accountingDocService->searchRowsAi($params, $params['acc'] ?? null);
|
||||
// ابزارهای مربوط به تیکت
|
||||
case 'getTicketsList':
|
||||
$cogTicketService = new \App\Cog\TicketService($this->em);
|
||||
$cogTicketService = new \App\Cog\TicketService($this->em, $this->explore, $this->jdate, $this->registryMGR, $this->sms, $this->uploadDirectory);
|
||||
$ticketService = new \App\AiTool\TicketService($this->em, $cogTicketService);
|
||||
return $ticketService->getTicketsListAi($params, $params['acc'] ?? null);
|
||||
case 'getTicketInfo':
|
||||
$cogTicketService = new \App\Cog\TicketService($this->em);
|
||||
$cogTicketService = new \App\Cog\TicketService($this->em, $this->explore, $this->jdate, $this->registryMGR, $this->sms, $this->uploadDirectory);
|
||||
$ticketService = new \App\AiTool\TicketService($this->em, $cogTicketService);
|
||||
return $ticketService->getTicketInfoByCode($params['code'] ?? null, $params['acc'] ?? null);
|
||||
case 'addOrUpdateTicket':
|
||||
$cogTicketService = new \App\Cog\TicketService($this->em);
|
||||
$cogTicketService = new \App\Cog\TicketService($this->em, $this->explore, $this->jdate, $this->registryMGR, $this->sms, $this->uploadDirectory);
|
||||
$ticketService = new \App\AiTool\TicketService($this->em, $cogTicketService);
|
||||
return $ticketService->addOrUpdateTicketAi($params, $params['acc'] ?? null, $params['code'] ?? 0);
|
||||
case 'replyToTicket':
|
||||
$cogTicketService = new \App\Cog\TicketService($this->em);
|
||||
$cogTicketService = new \App\Cog\TicketService($this->em, $this->explore, $this->jdate, $this->registryMGR, $this->sms, $this->uploadDirectory);
|
||||
$ticketService = new \App\AiTool\TicketService($this->em, $cogTicketService);
|
||||
return $ticketService->replyToTicketAi($params, $params['acc'] ?? null);
|
||||
default:
|
||||
|
|
|
@ -86,6 +86,8 @@ class PromptService
|
|||
switch ($key) {
|
||||
case 'person':
|
||||
return $this->personPromptService->getAllPersonPrompts();
|
||||
case 'ticket':
|
||||
return $this->ticketPromptService->getAllTicketPrompts();
|
||||
// در آینده موارد بیشتر اضافه خواهند شد
|
||||
// case 'accounting':
|
||||
// return $this->accountingPromptService->getAllAccountingPrompts();
|
||||
|
|
|
@ -2,204 +2,292 @@
|
|||
|
||||
namespace App\Service\AGI\Promps;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class TicketService
|
||||
{
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت ابزارهای مربوط به تیکتها
|
||||
* دریافت تمام ابزارهای بخش تیکتها برای function calling
|
||||
* @return array
|
||||
*/
|
||||
public function getTools(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => 'analyze_ticket',
|
||||
'description' => 'تحلیل و دستهبندی تیکت',
|
||||
'parameters' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'ticket_body' => [
|
||||
'type' => 'string',
|
||||
'description' => 'متن تیکت'
|
||||
]
|
||||
],
|
||||
'required' => ['ticket_body']
|
||||
$tools = [];
|
||||
|
||||
// ابزار getTicketsList
|
||||
$ticketsListPrompt = $this->getTicketsListPrompt();
|
||||
$ticketsListData = json_decode($ticketsListPrompt, true);
|
||||
if ($ticketsListData) {
|
||||
$tools[] = [
|
||||
'type' => 'function',
|
||||
'function' => [
|
||||
'name' => $ticketsListData['tool'],
|
||||
'description' => $ticketsListData['description'],
|
||||
'parameters' => $ticketsListData['parameters']
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'draft_ticket_response',
|
||||
'description' => 'تهیه پیشنویس پاسخ تیکت',
|
||||
'parameters' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'ticket_body' => [
|
||||
'type' => 'string',
|
||||
'description' => 'متن تیکت'
|
||||
],
|
||||
'ticket_title' => [
|
||||
'type' => 'string',
|
||||
'description' => 'عنوان تیکت'
|
||||
],
|
||||
'history' => [
|
||||
'type' => 'array',
|
||||
'description' => 'تاریخچه مکالمات قبلی',
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'sender' => [
|
||||
'type' => 'string',
|
||||
'description' => 'فرستنده پیام'
|
||||
],
|
||||
'message' => [
|
||||
'type' => 'string',
|
||||
'description' => 'متن پیام'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'required' => ['ticket_body', 'ticket_title']
|
||||
];
|
||||
}
|
||||
|
||||
// ابزار getTicketInfo
|
||||
$ticketInfoPrompt = $this->getTicketInfoPrompt();
|
||||
$ticketInfoData = json_decode($ticketInfoPrompt, true);
|
||||
if ($ticketInfoData) {
|
||||
$tools[] = [
|
||||
'type' => 'function',
|
||||
'function' => [
|
||||
'name' => $ticketInfoData['tool'],
|
||||
'description' => $ticketInfoData['description'],
|
||||
'parameters' => $ticketInfoData['parameters']
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپت برای بررسی متن تیکت و دستهبندی آن
|
||||
*/
|
||||
public function getTicketAnalysisPrompt(string $ticketBody): string
|
||||
{
|
||||
return <<<PROMPT
|
||||
لطفاً این تیکت پشتیبانی را بررسی و دستهبندی کنید:
|
||||
|
||||
متن تیکت:
|
||||
{$ticketBody}
|
||||
|
||||
لطفاً موارد زیر را مشخص کنید:
|
||||
1. موضوع اصلی تیکت
|
||||
2. اولویت (کم، متوسط، زیاد)
|
||||
3. بخش مربوطه (مالی، فنی، عمومی)
|
||||
4. پیشنهاد برای پاسخ
|
||||
PROMPT;
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپت برای تولید پیشنویس پاسخ به تیکت
|
||||
*/
|
||||
public function getDraftResponsePrompt(string $ticketBody, string $ticketTitle, array $history = []): string
|
||||
{
|
||||
$historyText = '';
|
||||
if (!empty($history)) {
|
||||
$historyText = "تاریخچه مکالمات قبلی:\n";
|
||||
foreach ($history as $message) {
|
||||
$historyText .= sprintf(
|
||||
"- %s: %s\n",
|
||||
$message['sender'],
|
||||
$message['message']
|
||||
);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return <<<PROMPT
|
||||
لطفاً یک پیشنویس پاسخ مناسب برای این تیکت پشتیبانی آماده کنید:
|
||||
|
||||
عنوان تیکت: {$ticketTitle}
|
||||
متن تیکت:
|
||||
{$ticketBody}
|
||||
|
||||
{$historyText}
|
||||
|
||||
لطفاً یک پاسخ حرفهای و دقیق با در نظر گرفتن نکات زیر آماده کنید:
|
||||
1. لحن مؤدبانه و حرفهای
|
||||
2. پاسخگویی به تمام نکات مطرح شده در تیکت
|
||||
3. ارائه راهکارهای عملی
|
||||
4. درخواست اطلاعات تکمیلی در صورت نیاز
|
||||
PROMPT;
|
||||
|
||||
// ابزار addOrUpdateTicket
|
||||
$addOrUpdatePrompt = $this->getAddOrUpdateTicketPrompt();
|
||||
$addOrUpdateData = json_decode($addOrUpdatePrompt, true);
|
||||
if ($addOrUpdateData) {
|
||||
$tools[] = [
|
||||
'type' => 'function',
|
||||
'function' => [
|
||||
'name' => $addOrUpdateData['tool'],
|
||||
'description' => $addOrUpdateData['description'],
|
||||
'parameters' => $addOrUpdateData['parameters']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// ابزار replyToTicket
|
||||
$replyPrompt = $this->getReplyToTicketPrompt();
|
||||
$replyData = json_decode($replyPrompt, true);
|
||||
if ($replyData) {
|
||||
$tools[] = [
|
||||
'type' => 'function',
|
||||
'function' => [
|
||||
'name' => $replyData['tool'],
|
||||
'description' => $replyData['description'],
|
||||
'parameters' => $replyData['parameters']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return $tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپت برای پیشنهاد اقدامات بعدی برای تیکت
|
||||
* تولید تمام پرامپهای بخش تیکتها
|
||||
* @return string
|
||||
*/
|
||||
public function getNextActionPrompt(string $ticketBody, string $currentStatus, array $previousActions = []): string
|
||||
public function getAllTicketPrompts(): string
|
||||
{
|
||||
$previousActionsText = '';
|
||||
if (!empty($previousActions)) {
|
||||
$previousActionsText = "اقدامات قبلی:\n";
|
||||
foreach ($previousActions as $action) {
|
||||
$previousActionsText .= "- {$action}\n";
|
||||
}
|
||||
}
|
||||
|
||||
return <<<PROMPT
|
||||
لطفاً اقدامات بعدی مناسب برای این تیکت را پیشنهاد دهید:
|
||||
|
||||
متن تیکت:
|
||||
{$ticketBody}
|
||||
|
||||
وضعیت فعلی: {$currentStatus}
|
||||
{$previousActionsText}
|
||||
|
||||
لطفاً موارد زیر را مشخص کنید:
|
||||
1. آیا نیاز به ارجاع به بخش دیگری هست؟
|
||||
2. آیا نیاز به اطلاعات تکمیلی از کاربر هست؟
|
||||
3. اولویت رسیدگی به این تیکت
|
||||
4. پیشنهاد برای اقدام بعدی
|
||||
PROMPT;
|
||||
$prompts = [];
|
||||
$prompts[] = $this->getTicketsListPrompt();
|
||||
$prompts[] = $this->getTicketInfoPrompt();
|
||||
$prompts[] = $this->getAddOrUpdateTicketPrompt();
|
||||
$prompts[] = $this->getReplyToTicketPrompt();
|
||||
return implode("\n\n", $prompts);
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپت برای خلاصهسازی تیکت و تاریخچه آن
|
||||
* پرامپ برای دریافت لیست تیکتها
|
||||
*/
|
||||
public function getTicketSummaryPrompt(array $ticketHistory): string
|
||||
public function getTicketsListPrompt(): string
|
||||
{
|
||||
$historyText = '';
|
||||
foreach ($ticketHistory as $entry) {
|
||||
$historyText .= sprintf(
|
||||
"- %s (%s): %s\n",
|
||||
$entry['date'],
|
||||
$entry['user'],
|
||||
$entry['message']
|
||||
);
|
||||
return '{
|
||||
"tool": "getTicketsList",
|
||||
"description": "دریافت لیست تیکتهای پشتیبانی با فیلتر و صفحهبندی",
|
||||
"endpoint": "/api/ticket/list",
|
||||
"method": "POST",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {"type": "integer", "description": "شماره صفحه"},
|
||||
"itemsPerPage": {"type": "integer", "description": "تعداد آیتم در هر صفحه"},
|
||||
"search": {"type": "string", "description": "متن جستوجو (عنوان، کد، و غیره)"},
|
||||
"status": {"type": "array", "items": {"type": "string"}, "description": "فیلتر وضعیت تیکتها (اختیاری)"},
|
||||
"priority": {"type": "array", "items": {"type": "string"}, "description": "فیلتر اولویت تیکتها (اختیاری)"},
|
||||
"sortBy": {"type": ["string", "null"], "description": "فیلد مرتبسازی (اختیاری)"},
|
||||
"acc": {"type": "object", "description": "اطلاعات دسترسی (مورد نیاز برای backend)"}
|
||||
},
|
||||
"required": ["page", "itemsPerPage", "search"]
|
||||
},
|
||||
"output": {
|
||||
"items": [
|
||||
{
|
||||
"id": "integer",
|
||||
"code": "string",
|
||||
"title": "string",
|
||||
"body": "string",
|
||||
"status": "string",
|
||||
"priority": "string",
|
||||
"dateSubmit": "integer",
|
||||
"submitter": "object",
|
||||
"main": "integer",
|
||||
"fileName": "string|null"
|
||||
}
|
||||
],
|
||||
"total": "integer",
|
||||
"unfilteredTotal": "integer"
|
||||
},
|
||||
"examples": {
|
||||
"input": {"page":1,"itemsPerPage":10,"search":"مشکل","status":["در حال پیگیری","بسته شده"],"priority":["کم","متوسط","زیاد"],"sortBy":null,"acc":{"bid":2,"user":2,"year":2,"access":true,"money":1,"ai":true}},
|
||||
"output": {
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"code": "TKT001",
|
||||
"title": "مشکل در ورود به سیستم",
|
||||
"body": "نمیتوانم وارد سیستم شوم",
|
||||
"status": "در حال پیگیری",
|
||||
"priority": "متوسط",
|
||||
"dateSubmit": 1703123456,
|
||||
"submitter": {"id": 1, "name": "کاربر نمونه"},
|
||||
"main": 0,
|
||||
"fileName": null
|
||||
}
|
||||
|
||||
return <<<PROMPT
|
||||
لطفاً خلاصهای از این تیکت و تاریخچه آن تهیه کنید:
|
||||
|
||||
تاریخچه تیکت:
|
||||
{$historyText}
|
||||
|
||||
لطفاً موارد زیر را در خلاصه مشخص کنید:
|
||||
1. موضوع اصلی و مشکل گزارش شده
|
||||
2. اقدامات انجام شده
|
||||
3. وضعیت فعلی
|
||||
4. نکات مهم برای پیگیری
|
||||
PROMPT;
|
||||
],
|
||||
"total": 1,
|
||||
"unfilteredTotal": 5
|
||||
}
|
||||
}
|
||||
}';
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپت برای دستهبندی خودکار تیکتها
|
||||
* پرامپ برای دریافت اطلاعات تیکت
|
||||
*/
|
||||
public function getTicketCategorizationPrompt(array $tickets): string
|
||||
public function getTicketInfoPrompt(): string
|
||||
{
|
||||
$ticketsText = '';
|
||||
foreach ($tickets as $ticket) {
|
||||
$ticketsText .= sprintf(
|
||||
"عنوان: %s\nمتن: %s\n\n",
|
||||
$ticket['title'],
|
||||
$ticket['body']
|
||||
);
|
||||
return '{
|
||||
"tool": "getTicketInfo",
|
||||
"description": "دریافت اطلاعات کامل یک تیکت بر اساس کد",
|
||||
"endpoint": "/api/ticket/info/{code}",
|
||||
"method": "GET",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {"type": "string", "description": "کد تیکت (مثل TKT001, TKT002)"},
|
||||
"acc": {"type": "object", "description": "اطلاعات دسترسی (مورد نیاز برای backend)"}
|
||||
},
|
||||
"required": ["code"]
|
||||
},
|
||||
"output": {
|
||||
"id": "integer",
|
||||
"code": "string",
|
||||
"title": "string",
|
||||
"body": "string",
|
||||
"status": "string",
|
||||
"priority": "string",
|
||||
"dateSubmit": "integer",
|
||||
"submitter": "object",
|
||||
"main": "integer",
|
||||
"fileName": "string|null",
|
||||
"replies": [
|
||||
{
|
||||
"id": "integer",
|
||||
"body": "string",
|
||||
"dateSubmit": "integer",
|
||||
"submitter": "object",
|
||||
"fileName": "string|null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"examples": {
|
||||
"input": {"code": "TKT001"},
|
||||
"output": {
|
||||
"id": 1,
|
||||
"code": "TKT001",
|
||||
"title": "مشکل در ورود به سیستم",
|
||||
"body": "نمیتوانم وارد سیستم شوم",
|
||||
"status": "در حال پیگیری",
|
||||
"priority": "متوسط",
|
||||
"dateSubmit": 1703123456,
|
||||
"submitter": {"id": 1, "name": "کاربر نمونه"},
|
||||
"main": 0,
|
||||
"fileName": null,
|
||||
"replies": [
|
||||
{
|
||||
"id": 2,
|
||||
"body": "لطفاً مرورگر خود را پاک کنید و دوباره تلاش کنید",
|
||||
"dateSubmit": 1703124000,
|
||||
"submitter": {"id": 2, "name": "پشتیبان"},
|
||||
"fileName": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}';
|
||||
}
|
||||
|
||||
return <<<PROMPT
|
||||
لطفاً این تیکتها را بر اساس موضوع و محتوا دستهبندی کنید:
|
||||
/**
|
||||
* پرامپ برای افزودن یا ویرایش تیکت
|
||||
*/
|
||||
public function getAddOrUpdateTicketPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "addOrUpdateTicket",
|
||||
"description": "برای ویرایش یک تیکت ابتدا باید با ابزار جستوجوی تیکت (getTicketsList) تیکت مورد نظر را پیدا کنید. اگر چند نتیجه یافت شد، باید از کاربر بپرسید کدام را میخواهد ویرایش کند و کد (code) آن را دریافت کنید. سپس با ارسال کد و اطلاعات جدید به این ابزار، ویرایش انجام میشود. اگر code برابر 0 یا ارسال نشود، تیکت جدید ایجاد خواهد شد. افزودن تیکت جدید یا ویرایش تیکت موجود",
|
||||
"endpoint": "/api/ticket/mod/{code}",
|
||||
"method": "POST",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string", "description": "عنوان تیکت (مورد نیاز)"},
|
||||
"body": {"type": "string", "description": "متن تیکت (مورد نیاز)"},
|
||||
"priority": {"type": "string", "description": "اولویت تیکت (کم، متوسط، زیاد)"},
|
||||
"status": {"type": "string", "description": "وضعیت تیکت (جدید، در حال پیگیری، بسته شده)"},
|
||||
"code": {"type": ["integer", "string"], "description": "کد تیکت (0 برای جدید، در غیر این صورت برای ویرایش)"},
|
||||
"acc": {"type": "object", "description": "اطلاعات دسترسی (مورد نیاز برای backend)"}
|
||||
},
|
||||
"required": ["title", "body"]
|
||||
},
|
||||
"output": {
|
||||
"Success": "boolean",
|
||||
"result": "integer",
|
||||
"message": "string"
|
||||
},
|
||||
"examples": {
|
||||
"input": {"title":"مشکل جدید","body":"نمیتوانم فایل آپلود کنم","priority":"متوسط","status":"جدید","code":0,"acc":{"bid":2,"user":2,"year":2,"access":true,"money":1,"ai":true}},
|
||||
"output": {"Success":true,"result":1,"message":"تیکت با موفقیت ایجاد شد"}
|
||||
}
|
||||
}';
|
||||
}
|
||||
|
||||
تیکتها:
|
||||
{$ticketsText}
|
||||
|
||||
لطفاً برای هر تیکت موارد زیر را مشخص کنید:
|
||||
1. دسته اصلی (مالی، فنی، پشتیبانی عمومی، آموزش)
|
||||
2. زیر دسته
|
||||
3. برچسبهای پیشنهادی
|
||||
4. اولویت پیشنهادی
|
||||
PROMPT;
|
||||
/**
|
||||
* پرامپ برای پاسخ به تیکت
|
||||
*/
|
||||
public function getReplyToTicketPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "replyToTicket",
|
||||
"description": "ارسال پاسخ به یک تیکت موجود",
|
||||
"endpoint": "/api/ticket/reply",
|
||||
"method": "POST",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ticketCode": {"type": "string", "description": "کد تیکت (مورد نیاز)"},
|
||||
"body": {"type": "string", "description": "متن پاسخ (مورد نیاز)"},
|
||||
"status": {"type": "string", "description": "وضعیت جدید تیکت (اختیاری)"},
|
||||
"acc": {"type": "object", "description": "اطلاعات دسترسی (مورد نیاز برای backend)"}
|
||||
},
|
||||
"required": ["ticketCode", "body"]
|
||||
},
|
||||
"output": {
|
||||
"Success": "boolean",
|
||||
"result": "integer",
|
||||
"message": "string"
|
||||
},
|
||||
"examples": {
|
||||
"input": {"ticketCode":"TKT001","body":"لطفاً مرورگر خود را پاک کنید و دوباره تلاش کنید","status":"در حال پیگیری","acc":{"bid":2,"user":2,"year":2,"access":true,"money":1,"ai":true}},
|
||||
"output": {"Success":true,"result":1,"message":"پاسخ با موفقیت ارسال شد"}
|
||||
}
|
||||
}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,6 +186,14 @@ const router = createRouter({
|
|||
'login': true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'manager/debug',
|
||||
component: () => import('../views/user/manager/debug/debug.vue'),
|
||||
meta: {
|
||||
'title': 'دیباگ سیستم',
|
||||
'login': true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'manager/changes/mod/:id',
|
||||
component: () => import('../views/user/manager/reportchange/mod.vue'),
|
||||
|
|
669
webUI/src/views/user/manager/debug/debug.vue
Normal file
669
webUI/src/views/user/manager/debug/debug.vue
Normal file
|
@ -0,0 +1,669 @@
|
|||
<template>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-card>
|
||||
<v-card-title class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<v-icon class="mr-2">mdi-bug</v-icon>
|
||||
مدیریت دیباگ سیستم
|
||||
<v-chip
|
||||
v-if="environment"
|
||||
:color="getEnvironmentColor(environment)"
|
||||
size="small"
|
||||
class="ml-2"
|
||||
>
|
||||
{{ environment.toUpperCase() }}
|
||||
</v-chip>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="outlined"
|
||||
:disabled="selectedLogs.length === 0"
|
||||
prepend-icon="mdi-delete"
|
||||
class="mr-2"
|
||||
@click="showDeleteDialog = true"
|
||||
>
|
||||
حذف انتخاب شده ({{ selectedLogs.length }})
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-delete-sweep"
|
||||
@click="showDeleteAllDialog = true"
|
||||
>
|
||||
حذف همه
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<!-- فیلترها -->
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="3">
|
||||
<v-text-field
|
||||
v-model="filters.search"
|
||||
label="جستجو"
|
||||
prepend-icon="mdi-magnify"
|
||||
clearable
|
||||
@update:model-value="debouncedLoadLogs"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2">
|
||||
<v-select
|
||||
v-model="filters.level"
|
||||
label="سطح لاگ"
|
||||
:items="logLevels"
|
||||
clearable
|
||||
@update:model-value="loadLogs"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2">
|
||||
<v-text-field
|
||||
v-model="filters.date"
|
||||
label="تاریخ"
|
||||
type="date"
|
||||
clearable
|
||||
@update:model-value="loadLogs"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2">
|
||||
<v-select
|
||||
v-model="pagination.limit"
|
||||
label="تعداد در صفحه"
|
||||
:items="[10, 25, 50, 100]"
|
||||
@update:model-value="loadLogs"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
|
||||
<!-- اطلاعات سیستم -->
|
||||
<v-card-text v-if="systemInfo">
|
||||
<v-alert
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
>
|
||||
<div>
|
||||
<strong>اطلاعات سیستم:</strong>
|
||||
محیط: <v-chip :color="getEnvironmentColor(systemInfo.environment)" size="small">{{ systemInfo.environment.toUpperCase() }}</v-chip> |
|
||||
فایلهای لاگ: {{ systemInfo.log_files_count }} |
|
||||
حجم کل: {{ formatBytes(systemInfo.log_files_size) }} |
|
||||
حافظه استفاده شده: {{ formatBytes(systemInfo.memory_usage) }}
|
||||
</div>
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
|
||||
<!-- جدول لاگها -->
|
||||
<v-data-table
|
||||
v-model="selectedLogs"
|
||||
:headers="headers"
|
||||
:items="logs"
|
||||
:loading="loading"
|
||||
:items-per-page="pagination.limit"
|
||||
:page="pagination.page"
|
||||
:total-items="pagination.total"
|
||||
:sort-by="sortBy"
|
||||
:sort-desc="sortDesc"
|
||||
show-select
|
||||
item-key="id"
|
||||
class="elevation-1"
|
||||
@update:options="handleTableUpdate"
|
||||
>
|
||||
<template v-slot:item.level="{ item }">
|
||||
<v-chip
|
||||
:color="getLevelColor(item.level)"
|
||||
size="small"
|
||||
variant="flat"
|
||||
>
|
||||
{{ item.level }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.timestamp="{ item }">
|
||||
<div>
|
||||
<div class="text-body-2">{{ item.date }}</div>
|
||||
<div class="text-caption text-grey">{{ item.time }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.message="{ item }">
|
||||
<div class="text-truncate" style="max-width: 300px;">
|
||||
{{ item.message }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.environment="{ item }">
|
||||
<v-chip
|
||||
:color="getEnvironmentColor(item.environment)"
|
||||
size="small"
|
||||
variant="flat"
|
||||
>
|
||||
{{ item.environment.toUpperCase() }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="text"
|
||||
@click="viewLogDetail(item)"
|
||||
prepend-icon="mdi-eye"
|
||||
>
|
||||
مشاهده
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
|
||||
<!-- صفحهبندی -->
|
||||
<v-card-actions class="justify-center">
|
||||
<v-pagination
|
||||
v-model="pagination.page"
|
||||
:length="pagination.totalPages"
|
||||
:total-visible="7"
|
||||
@update:model-value="loadLogs"
|
||||
/>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- دیالوگ جزئیات لاگ -->
|
||||
<v-dialog v-model="showDetailDialog" max-width="800px">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<v-icon class="mr-2">mdi-file-document</v-icon>
|
||||
جزئیات لاگ
|
||||
<v-chip
|
||||
v-if="selectedLog?.environment"
|
||||
:color="getEnvironmentColor(selectedLog.environment)"
|
||||
size="small"
|
||||
class="ml-2"
|
||||
>
|
||||
{{ selectedLog.environment.toUpperCase() }}
|
||||
</v-chip>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row v-if="selectedLog">
|
||||
<v-col cols="12" md="6">
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-calendar</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>تاریخ</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ selectedLog.date }} {{ selectedLog.time }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-alert-circle</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>سطح</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
<v-chip :color="getLevelColor(selectedLog.level)" size="small">
|
||||
{{ selectedLog.level }}
|
||||
</v-chip>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-file</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>فایل</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ selectedLog.filename }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item v-if="selectedLog.environment">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-server</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>محیط</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
<v-chip :color="getEnvironmentColor(selectedLog.environment)" size="small">
|
||||
{{ selectedLog.environment.toUpperCase() }}
|
||||
</v-chip>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-textarea
|
||||
v-model="selectedLog.message"
|
||||
label="پیام"
|
||||
readonly
|
||||
rows="4"
|
||||
variant="outlined"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" v-if="selectedLog.extra && Object.keys(selectedLog.extra).length > 0">
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel>
|
||||
<v-expansion-panel-title>
|
||||
اطلاعات اضافی
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
<pre class="text-body-2">{{ JSON.stringify(selectedLog.extra, null, 2) }}</pre>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
v-model="selectedLog.raw"
|
||||
label="متن خام"
|
||||
readonly
|
||||
rows="6"
|
||||
variant="outlined"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn color="primary" @click="showDetailDialog = false">
|
||||
بستن
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- دیالوگ حذف انتخاب شده -->
|
||||
<v-dialog v-model="showDeleteDialog" max-width="400px">
|
||||
<v-card>
|
||||
<v-card-title class="text-h6">
|
||||
<v-icon class="mr-2" color="error">mdi-delete</v-icon>
|
||||
حذف لاگهای انتخاب شده
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
آیا از حذف {{ selectedLogs.length }} لاگ انتخاب شده اطمینان دارید؟
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn @click="showDeleteDialog = false">انصراف</v-btn>
|
||||
<v-btn color="error" @click="deleteSelectedLogs" :loading="deleting">
|
||||
حذف
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- دیالوگ حذف همه -->
|
||||
<v-dialog v-model="showDeleteAllDialog" max-width="400px">
|
||||
<v-card>
|
||||
<v-card-title class="text-h6">
|
||||
<v-icon class="mr-2" color="warning">mdi-delete-sweep</v-icon>
|
||||
حذف تمام لاگها
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
آیا از حذف تمام لاگهای سیستم اطمینان دارید؟ این عملیات غیرقابل بازگشت است.
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn @click="showDeleteAllDialog = false">انصراف</v-btn>
|
||||
<v-btn color="warning" @click="deleteAllLogs" :loading="deleting">
|
||||
حذف همه
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- اسنکبار -->
|
||||
<v-snackbar
|
||||
v-model="snackbar.show"
|
||||
:color="snackbar.color"
|
||||
:timeout="snackbar.timeout"
|
||||
>
|
||||
{{ snackbar.message }}
|
||||
<template v-slot:actions>
|
||||
<v-btn
|
||||
color="white"
|
||||
variant="text"
|
||||
@click="snackbar.show = false"
|
||||
>
|
||||
بستن
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'Debug',
|
||||
setup() {
|
||||
const loading = ref(false)
|
||||
const loadingSystemInfo = ref(false)
|
||||
const deleting = ref(false)
|
||||
const logs = ref([])
|
||||
const selectedLogs = ref([])
|
||||
const selectedLog = ref(null)
|
||||
const showDetailDialog = ref(false)
|
||||
const showDeleteDialog = ref(false)
|
||||
const showDeleteAllDialog = ref(false)
|
||||
const systemInfo = ref(null)
|
||||
const environment = ref('')
|
||||
|
||||
const filters = reactive({
|
||||
search: '',
|
||||
level: '',
|
||||
date: ''
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
limit: 50,
|
||||
total: 0,
|
||||
totalPages: 0
|
||||
})
|
||||
|
||||
const snackbar = reactive({
|
||||
show: false,
|
||||
message: '',
|
||||
color: 'success',
|
||||
timeout: 3000
|
||||
})
|
||||
|
||||
// متغیرهای مرتبسازی
|
||||
const sortBy = ref(['timestamp'])
|
||||
const sortDesc = ref([true])
|
||||
|
||||
const headers = [
|
||||
{ title: 'تاریخ', key: 'timestamp', sortable: true },
|
||||
{ title: 'سطح', key: 'level', sortable: true },
|
||||
{ title: 'پیام', key: 'message', sortable: false },
|
||||
{ title: 'فایل', key: 'filename', sortable: true },
|
||||
{ title: 'محیط', key: 'environment', sortable: true },
|
||||
{ title: 'عملیات', key: 'actions', sortable: false }
|
||||
]
|
||||
|
||||
const logLevels = [
|
||||
'DEBUG',
|
||||
'INFO',
|
||||
'WARNING',
|
||||
'ERROR',
|
||||
'CRITICAL',
|
||||
'ALERT',
|
||||
'EMERGENCY'
|
||||
]
|
||||
|
||||
// Debounce برای جستجو
|
||||
let searchTimeout = null
|
||||
const debouncedLoadLogs = () => {
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout)
|
||||
}
|
||||
searchTimeout = setTimeout(() => {
|
||||
pagination.page = 1
|
||||
loadLogs()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const getLevelColor = (level) => {
|
||||
const colors = {
|
||||
'DEBUG': 'grey',
|
||||
'INFO': 'blue',
|
||||
'WARNING': 'orange',
|
||||
'ERROR': 'red',
|
||||
'CRITICAL': 'red-darken-2',
|
||||
'ALERT': 'red-darken-3',
|
||||
'EMERGENCY': 'red-darken-4'
|
||||
}
|
||||
return colors[level] || 'grey'
|
||||
}
|
||||
|
||||
const getEnvironmentColor = (env) => {
|
||||
const colors = {
|
||||
'dev': 'green',
|
||||
'prod': 'red',
|
||||
'test': 'orange'
|
||||
}
|
||||
return colors[env] || 'blue'
|
||||
}
|
||||
|
||||
const formatBytes = (bytes) => {
|
||||
if (bytes === 0) return '0 Bytes'
|
||||
const k = 1024
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
const loadLogs = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
// Get current sorting values
|
||||
const currentSortBy = sortBy.value[0] || 'timestamp'
|
||||
const currentSortDesc = sortDesc.value[0] || true
|
||||
|
||||
// Create simple params object
|
||||
const params = {
|
||||
page: pagination.page,
|
||||
limit: pagination.limit,
|
||||
search: filters.search || '',
|
||||
level: filters.level || '',
|
||||
date: filters.date || '',
|
||||
sortBy: currentSortBy,
|
||||
sortDesc: currentSortDesc
|
||||
}
|
||||
|
||||
console.log('Sending params:', params)
|
||||
|
||||
const response = await axios.get('/api/admin/debug/logs', { params })
|
||||
|
||||
if (response.data.success) {
|
||||
logs.value = response.data.data
|
||||
pagination.total = response.data.total
|
||||
pagination.totalPages = response.data.totalPages
|
||||
environment.value = response.data.environment
|
||||
} else {
|
||||
showSnackbar('خطا در دریافت لاگها', 'error')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading logs:', error)
|
||||
showSnackbar('خطا در دریافت لاگها', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadSystemInfo = async () => {
|
||||
try {
|
||||
loadingSystemInfo.value = true
|
||||
const response = await axios.get('/api/admin/debug/system-info')
|
||||
|
||||
if (response.data.success) {
|
||||
systemInfo.value = response.data.data
|
||||
environment.value = response.data.data.environment
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading system info:', error)
|
||||
} finally {
|
||||
loadingSystemInfo.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const handleTableUpdate = (options) => {
|
||||
try {
|
||||
console.log('Table update options:', options)
|
||||
|
||||
let shouldReload = false
|
||||
|
||||
// بررسی تغییرات صفحهبندی
|
||||
if (options && options.page !== undefined && options.page !== pagination.page) {
|
||||
pagination.page = options.page
|
||||
shouldReload = true
|
||||
}
|
||||
|
||||
// بررسی تغییرات مرتبسازی
|
||||
if (options && options.sortBy && Array.isArray(options.sortBy) && options.sortBy.length > 0) {
|
||||
const newSortBy = options.sortBy[0]
|
||||
// بررسی وجود sortDesc و مقدار آن
|
||||
const newSortDesc = options.sortDesc && Array.isArray(options.sortDesc) && options.sortDesc.length > 0
|
||||
? options.sortDesc[0]
|
||||
: true
|
||||
|
||||
console.log('Sorting changed:', { newSortBy, newSortDesc, currentSortBy: sortBy.value[0], currentSortDesc: sortDesc.value[0] })
|
||||
|
||||
if (newSortBy !== sortBy.value[0] || newSortDesc !== sortDesc.value[0]) {
|
||||
sortBy.value = [newSortBy]
|
||||
sortDesc.value = [newSortDesc]
|
||||
shouldReload = true
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldReload) {
|
||||
loadLogs()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in handleTableUpdate:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const viewLogDetail = async (log) => {
|
||||
try {
|
||||
const response = await axios.get(`/api/admin/debug/logs/${log.id}`)
|
||||
|
||||
if (response.data.success) {
|
||||
selectedLog.value = response.data.data
|
||||
showDetailDialog.value = true
|
||||
} else {
|
||||
showSnackbar('خطا در دریافت جزئیات لاگ', 'error')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading log detail:', error)
|
||||
showSnackbar('خطا در دریافت جزئیات لاگ', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const deleteSelectedLogs = async () => {
|
||||
try {
|
||||
deleting.value = true
|
||||
const logIds = selectedLogs.value.map(log => log.id)
|
||||
|
||||
const response = await axios.delete('/api/admin/debug/logs', {
|
||||
data: { ids: logIds }
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
showSnackbar(response.data.message, 'success')
|
||||
selectedLogs.value = []
|
||||
pagination.page = 1
|
||||
loadLogs()
|
||||
loadSystemInfo()
|
||||
} else {
|
||||
showSnackbar('خطا در حذف لاگها', 'error')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting logs:', error)
|
||||
showSnackbar('خطا در حذف لاگها', 'error')
|
||||
} finally {
|
||||
deleting.value = false
|
||||
showDeleteDialog.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAllLogs = async () => {
|
||||
try {
|
||||
deleting.value = true
|
||||
|
||||
const response = await axios.delete('/api/admin/debug/logs', {
|
||||
data: { deleteAll: true }
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
showSnackbar(response.data.message, 'success')
|
||||
selectedLogs.value = []
|
||||
pagination.page = 1
|
||||
loadLogs()
|
||||
loadSystemInfo()
|
||||
} else {
|
||||
showSnackbar('خطا در حذف لاگها', 'error')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting all logs:', error)
|
||||
showSnackbar('خطا در حذف لاگها', 'error')
|
||||
} finally {
|
||||
deleting.value = false
|
||||
showDeleteAllDialog.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const showSnackbar = (message, color = 'success') => {
|
||||
snackbar.message = message
|
||||
snackbar.color = color
|
||||
snackbar.show = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadLogs()
|
||||
loadSystemInfo()
|
||||
})
|
||||
|
||||
return {
|
||||
loading,
|
||||
loadingSystemInfo,
|
||||
deleting,
|
||||
logs,
|
||||
selectedLogs,
|
||||
selectedLog,
|
||||
showDetailDialog,
|
||||
showDeleteDialog,
|
||||
showDeleteAllDialog,
|
||||
systemInfo,
|
||||
environment,
|
||||
filters,
|
||||
pagination,
|
||||
snackbar,
|
||||
headers,
|
||||
logLevels,
|
||||
getLevelColor,
|
||||
getEnvironmentColor,
|
||||
formatBytes,
|
||||
loadLogs,
|
||||
loadSystemInfo,
|
||||
handleTableUpdate,
|
||||
viewLogDetail,
|
||||
deleteSelectedLogs,
|
||||
deleteAllLogs,
|
||||
showSnackbar,
|
||||
debouncedLoadLogs,
|
||||
sortBy,
|
||||
sortDesc
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-data-table {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.v-chip {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #f5f5f5;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -113,6 +113,7 @@ export default defineComponent({
|
|||
{ text: 'تاریخچه سیستم', url: '/profile/manager/logs/list', icon: 'mdi-history', visible: true },
|
||||
{ text: 'کیف پول', url: '/profile/manager/wallet/list', icon: 'mdi-wallet', visible: true },
|
||||
{ text: 'اطلاعیهها', url: '/profile/manager/statments/list', icon: 'mdi-bell', visible: true },
|
||||
{ text: 'دیباگ سیستم', url: '/profile/manager/debug', icon: 'mdi-bug', visible: true },
|
||||
],
|
||||
adminSettings: [
|
||||
{ text: 'پیامک', url: '/profile/manager/system/sms/settings', icon: 'mdi-message-alert', visible: true },
|
||||
|
|
Loading…
Reference in a new issue