Files
sam-manage/resources/views/api-logs/index.blade.php
hskwon e7beefd594 API 로그 페이지 개선: 테넌트/사용자 표시, 그룹핑, AI 분석 복사
- 테넌트/사용자 컬럼 추가 (관계 eager loading)
- 그룹 ID로 연관 API 호출 필터링 및 상세 페이지에서 그룹 목록 표시
- 상태 400 이상일 때 AI 분석용 복사 버튼 추가
- Tenant 모델 네임스페이스 수정 (Tenants\Tenant)
2025-12-15 16:33:58 +09:00

185 lines
9.3 KiB
PHP

@extends('layouts.app')
@section('title', 'API 로그')
@section('content')
<!-- 페이지 헤더 -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">API 요청 로그</h1>
<div class="flex gap-2">
<form action="{{ route('dev-tools.api-logs.prune') }}" method="POST" onsubmit="return confirm('하루 지난 로그를 삭제하시겠습니까?')">
@csrf
<button type="submit" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg transition">
오래된 로그 삭제
</button>
</form>
</div>
</div>
<!-- 알림 메시지 -->
@if(session('success'))
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
{{ session('success') }}
</div>
@endif
<!-- 통계 카드 -->
<div class="grid grid-cols-1 md:grid-cols-5 gap-4 mb-6">
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">전체 요청</div>
<div class="text-2xl font-bold text-gray-800">{{ number_format($stats['total']) }}</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">성공 (2xx)</div>
<div class="text-2xl font-bold text-green-600">{{ number_format($stats['success']) }}</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">클라이언트 에러 (4xx)</div>
<div class="text-2xl font-bold text-yellow-600">{{ number_format($stats['client_error']) }}</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">서버 에러 (5xx)</div>
<div class="text-2xl font-bold text-red-600">{{ number_format($stats['server_error']) }}</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">평균 응답시간</div>
<div class="text-2xl font-bold text-blue-600">{{ number_format($stats['avg_duration']) }}ms</div>
</div>
</div>
<!-- 필터 -->
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
<form method="GET" class="flex flex-wrap gap-4 items-end">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">메서드</label>
<select name="method" class="border rounded-lg px-3 py-2 text-sm">
<option value="">전체</option>
<option value="GET" {{ request('method') === 'GET' ? 'selected' : '' }}>GET</option>
<option value="POST" {{ request('method') === 'POST' ? 'selected' : '' }}>POST</option>
<option value="PUT" {{ request('method') === 'PUT' ? 'selected' : '' }}>PUT</option>
<option value="PATCH" {{ request('method') === 'PATCH' ? 'selected' : '' }}>PATCH</option>
<option value="DELETE" {{ request('method') === 'DELETE' ? 'selected' : '' }}>DELETE</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">상태</label>
<select name="status" class="border rounded-lg px-3 py-2 text-sm">
<option value="">전체</option>
<option value="2xx" {{ request('status') === '2xx' ? 'selected' : '' }}>성공 (2xx)</option>
<option value="4xx" {{ request('status') === '4xx' ? 'selected' : '' }}>클라이언트 에러 (4xx)</option>
<option value="5xx" {{ request('status') === '5xx' ? 'selected' : '' }}>서버 에러 (5xx)</option>
</select>
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 mb-1">URL 검색</label>
<input type="text" name="search" value="{{ request('search') }}"
placeholder="URL 검색..."
class="w-full border rounded-lg px-3 py-2 text-sm">
</div>
<div>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm">
검색
</button>
<a href="{{ route('dev-tools.api-logs.index') }}" class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg text-sm ml-1">
초기화
</a>
</div>
</form>
</div>
<!-- 그룹 필터 안내 -->
@if(request('group_id'))
<div class="bg-blue-50 border border-blue-200 text-blue-700 px-4 py-3 rounded mb-4 flex justify-between items-center">
<span>그룹 ID: <code class="bg-blue-100 px-2 py-1 rounded">{{ request('group_id') }}</code> 필터 적용 </span>
<a href="{{ route('dev-tools.api-logs.index') }}" class="text-blue-600 hover:text-blue-800 underline">필터 해제</a>
</div>
@endif
<!-- 로그 목록 -->
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">시간</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">메서드</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">URL</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">상태</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">응답</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">테넌트</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">사용자</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">그룹</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"></th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($logs as $log)
<tr class="hover:bg-gray-50 {{ $log->response_status >= 400 ? 'bg-red-50' : '' }}">
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{{ $log->created_at->format('H:i:s') }}
</td>
<td class="px-4 py-3 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-medium rounded {{ $log->method_color }}">
{{ $log->method }}
</span>
</td>
<td class="px-4 py-3 text-sm text-gray-900 max-w-xs truncate" title="{{ $log->url }}">
{{ $log->path }}
</td>
<td class="px-4 py-3 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-medium rounded {{ $log->status_color }}">
{{ $log->response_status }}
</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{{ number_format($log->duration_ms) }}ms
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
@if($log->tenant)
<span title="{{ $log->tenant->company_name }}">{{ Str::limit($log->tenant->company_name, 10) }}</span>
@else
<span class="text-gray-400">-</span>
@endif
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
@if($log->user)
<span title="{{ $log->user->email }}">{{ $log->user->name ?? $log->user->email }}</span>
@else
<span class="text-gray-400">guest</span>
@endif
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm">
@if($log->group_id)
<a href="{{ route('dev-tools.api-logs.index', ['group_id' => $log->group_id]) }}"
class="text-purple-600 hover:text-purple-800" title="{{ $log->group_id }}">
<svg class="w-4 h-4 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
</svg>
</a>
@else
<span class="text-gray-400">-</span>
@endif
</td>
<td class="px-4 py-3 whitespace-nowrap text-right text-sm">
<a href="{{ route('dev-tools.api-logs.show', $log->id) }}" class="text-blue-600 hover:text-blue-800">
상세
</a>
</td>
</tr>
@empty
<tr>
<td colspan="9" class="px-4 py-8 text-center text-gray-500">
로그가 없습니다.
</td>
</tr>
@endforelse
</tbody>
</table>
<!-- 페이지네이션 -->
@if($logs->hasPages())
<div class="px-4 py-3 border-t">
{{ $logs->links() }}
</div>
@endif
</div>
@endsection