diff --git a/app/Http/Controllers/ApiLogController.php b/app/Http/Controllers/ApiLogController.php index 4b8a2520..29d9ae2b 100644 --- a/app/Http/Controllers/ApiLogController.php +++ b/app/Http/Controllers/ApiLogController.php @@ -13,9 +13,17 @@ class ApiLogController extends Controller */ public function index(Request $request): View { + // 필터가 적용되면 오래된 순, 아니면 최신순 + $hasFilter = $request->hasAny(['method', 'status', 'search', 'group_id', 'tenant_id']); + $query = ApiRequestLog::query() - ->with(['tenant', 'user']) - ->orderByDesc('created_at'); + ->with(['tenant', 'user']); + + if ($hasFilter) { + $query->orderBy('created_at'); // 오래된 순 + } else { + $query->orderByDesc('created_at'); // 최신순 + } // 필터: HTTP 메서드 if ($request->filled('method')) { @@ -81,16 +89,23 @@ public function show(int $id): View { $log = ApiRequestLog::with(['tenant', 'user'])->findOrFail($id); - // 같은 그룹의 다른 요청들 - $groupLogs = []; + // 같은 그룹의 다른 요청들 (오래된 순) + $groupLogs = collect(); + $groupMethodCounts = []; if ($log->group_id) { $groupLogs = ApiRequestLog::where('group_id', $log->group_id) ->where('id', '!=', $log->id) - ->orderByDesc('created_at') + ->orderBy('created_at') // 오래된 순 ->get(); + + // 메서드별 개수 집계 + $groupMethodCounts = $groupLogs->groupBy('method') + ->map(fn($items) => $items->count()) + ->sortKeys() + ->toArray(); } - return view('api-logs.show', compact('log', 'groupLogs')); + return view('api-logs.show', compact('log', 'groupLogs', 'groupMethodCounts')); } /** diff --git a/resources/views/api-logs/index.blade.php b/resources/views/api-logs/index.blade.php index 2d3f7554..0582192b 100644 --- a/resources/views/api-logs/index.blade.php +++ b/resources/views/api-logs/index.blade.php @@ -7,21 +7,47 @@

API 요청 로그

-
+ @csrf -
-
+ @csrf -
+ + + @if(session('success'))
@@ -230,7 +256,7 @@ class="text-purple-600 hover:text-purple-800" title="{{ $log->group_id }} ({{ $g @php $responseData = json_decode($log->response_body, true); if ($responseData !== null) { - $displayResponse = json_encode($responseData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + $displayResponse = stripslashes(json_encode($responseData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); } else { $displayResponse = preg_replace_callback('/\\\\u([0-9a-fA-F]{4})/', function($m) { return mb_convert_encoding(pack('H*', $m[1]), 'UTF-8', 'UTF-16BE'); @@ -238,7 +264,7 @@ class="text-purple-600 hover:text-purple-800" title="{{ $log->group_id }} ({{ $g $displayResponse = str_replace('\\/', '/', $displayResponse); } @endphp -
{{ Str::limit($displayResponse, 2000) }}
+
{!! Str::limit($displayResponse, 2000) !!}
@@ -274,13 +300,88 @@ function copyAiAnalysis(id) { const textarea = document.getElementById('ai-analysis-' + id); if (textarea) { navigator.clipboard.writeText(textarea.value).then(() => { - alert('AI 분석용 내용이 클립보드에 복사되었습니다.\nClaude나 ChatGPT에 붙여넣기 하세요.'); + showAlertModal('복사 완료', 'AI 분석용 내용이 클립보드에 복사되었습니다.\nClaude나 ChatGPT에 붙여넣기 하세요.', 'green'); }).catch(err => { console.error('복사 실패:', err); - alert('복사에 실패했습니다.'); + showAlertModal('복사 실패', '복사에 실패했습니다.', 'red'); }); } } + +// 커스텀 확인 모달 +let currentFormId = null; + +function showConfirmModal(formId, title, message, color) { + currentFormId = formId; + + const modal = document.getElementById('confirmModal'); + const modalIcon = document.getElementById('modalIcon'); + const modalTitle = document.getElementById('modalTitle'); + const modalMessage = document.getElementById('modalMessage'); + const modalConfirmBtn = document.getElementById('modalConfirmBtn'); + + modalTitle.textContent = title; + modalMessage.textContent = message; + + // 색상 설정 + const colors = { + yellow: { icon: 'bg-yellow-500', btn: 'bg-yellow-600 hover:bg-yellow-700' }, + red: { icon: 'bg-red-500', btn: 'bg-red-600 hover:bg-red-700' }, + green: { icon: 'bg-green-500', btn: 'bg-green-600 hover:bg-green-700' }, + blue: { icon: 'bg-blue-500', btn: 'bg-blue-600 hover:bg-blue-700' } + }; + const colorSet = colors[color] || colors.blue; + + modalIcon.className = `w-10 h-10 rounded-full flex items-center justify-center ${colorSet.icon}`; + modalConfirmBtn.className = `px-4 py-2 text-white rounded-lg transition ${colorSet.btn}`; + + modal.classList.remove('hidden'); +} + +function hideConfirmModal() { + const modal = document.getElementById('confirmModal'); + modal.classList.add('hidden'); + currentFormId = null; +} + +function confirmAction() { + if (currentFormId) { + const form = document.getElementById(currentFormId + 'Form'); + if (form) { + form.submit(); + } + } + hideConfirmModal(); +} + +// 커스텀 알림 모달 (alert 대체) +function showAlertModal(title, message, color = 'blue') { + const modal = document.getElementById('confirmModal'); + const modalIcon = document.getElementById('modalIcon'); + const modalTitle = document.getElementById('modalTitle'); + const modalMessage = document.getElementById('modalMessage'); + const modalConfirmBtn = document.getElementById('modalConfirmBtn'); + + modalTitle.textContent = title; + modalMessage.textContent = message; + + const colors = { + yellow: { icon: 'bg-yellow-500', btn: 'bg-yellow-600 hover:bg-yellow-700' }, + red: { icon: 'bg-red-500', btn: 'bg-red-600 hover:bg-red-700' }, + green: { icon: 'bg-green-500', btn: 'bg-green-600 hover:bg-green-700' }, + blue: { icon: 'bg-blue-500', btn: 'bg-blue-600 hover:bg-blue-700' } + }; + const colorSet = colors[color] || colors.blue; + + modalIcon.className = `w-10 h-10 rounded-full flex items-center justify-center ${colorSet.icon}`; + modalConfirmBtn.className = `px-4 py-2 text-white rounded-lg transition ${colorSet.btn}`; + modalConfirmBtn.textContent = '확인'; + + // 알림 모드: 확인 버튼만 닫기 기능으로 변경 + currentFormId = null; + + modal.classList.remove('hidden'); +} diff --git a/resources/views/api-logs/show.blade.php b/resources/views/api-logs/show.blade.php index f6cffdf5..fbd6cd51 100644 --- a/resources/views/api-logs/show.blade.php +++ b/resources/views/api-logs/show.blade.php @@ -104,57 +104,93 @@ class="text-purple-600 hover:text-purple-800 font-mono text-xs"> @if($log->group_id && count($groupLogs) > 0) -
-

- 같은 그룹의 요청 ({{ count($groupLogs) }}개) - - 전체 보기 - -

-
- - - - - - - - - - - - - @foreach($groupLogs as $gLog) - - - - - - - - +
+ +
+
+

+ 같은 그룹의 요청 +

+ +
+ @foreach($groupMethodCounts as $method => $count) + @php + $methodColors = [ + 'GET' => 'bg-blue-100 text-blue-800', + 'POST' => 'bg-green-100 text-green-800', + 'PUT' => 'bg-yellow-100 text-yellow-800', + 'PATCH' => 'bg-orange-100 text-orange-800', + 'DELETE' => 'bg-red-100 text-red-800', + ]; + $color = $methodColors[$method] ?? 'bg-gray-100 text-gray-800'; + @endphp + + {{ $method }} {{ $count }} + @endforeach -
-
시간메서드URL상태응답
- {{ $gLog->created_at->format('H:i:s.u') }} - - - {{ $gLog->method }} - - - {{ $gLog->path }} - - - {{ $gLog->response_status }} - - - {{ number_format($gLog->duration_ms) }}ms - - - 상세 - -
+ + 총 {{ count($groupLogs) }}개 + +
+
+
+ + 전체 보기 + + + + +
+ + + @endif diff --git a/resources/views/daily-logs/index.blade.php b/resources/views/daily-logs/index.blade.php index c445d4d3..46f60539 100644 --- a/resources/views/daily-logs/index.blade.php +++ b/resources/views/daily-logs/index.blade.php @@ -609,7 +609,7 @@ function nl2br(text) { // 이슈/태스크 상태 변경 기능 // ======================================== function updateIssueStatus(issueId, status) { - fetch(`/admin/pm/issues/${issueId}/status`, { + fetch(`/api/admin/pm/issues/${issueId}/status`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -643,7 +643,7 @@ function updateIssueStatus(issueId, status) { } function updateTaskStatus(taskId, status) { - fetch(`/admin/pm/tasks/${taskId}/status`, { + fetch(`/api/admin/pm/tasks/${taskId}/status`, { method: 'POST', headers: { 'Content-Type': 'application/json',