Files
sam-manage/resources/views/trigger-audit/index.blade.php
권혁성 8f20fa8163 feat:트리거 관리(4.4) + 파티션 관리(4.6) UI 구현
- TriggerManagementService: 테이블별 트리거 상태 조회/재생성/삭제
- PartitionManagementService: 파티션 현황 조회/추가/삭제 (보관기간 검증)
- triggers.blade.php: 트리거 상태 대시보드 + 개별/전체 재생성·삭제
- partitions.blade.php: 파티션 통계 + 추가/삭제 (초과분만)
- sub-nav: 감사 로그 목록/트리거 관리/파티션 관리 탭 내비게이션
- 라우트 6개 추가, 컨트롤러 6개 메서드 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 19:53:32 +09:00

194 lines
8.8 KiB
PHP

@extends('layouts.app')
@section('title', 'DB 트리거 감사 로그')
@section('content')
<!-- 페이지 헤더 -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">DB 트리거 감사 로그</h1>
<div class="flex items-center gap-2 text-sm text-gray-500">
<span class="inline-flex items-center px-2 py-1 bg-green-100 text-green-700 rounded">트리거 {{ $triggerCount }} 활성</span>
</div>
</div>
@include('trigger-audit.partials.sub-nav')
<!-- 통계 카드 -->
<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">오늘 기록</div>
<div class="text-2xl font-bold text-blue-600">{{ number_format($stats['today']) }}</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">INSERT</div>
<div class="text-2xl font-bold text-green-600">{{ number_format($stats['by_dml_type']['INSERT'] ?? 0) }}</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">UPDATE</div>
<div class="text-2xl font-bold text-yellow-600">{{ number_format($stats['by_dml_type']['UPDATE'] ?? 0) }}</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">DELETE</div>
<div class="text-2xl font-bold text-red-600">{{ number_format($stats['by_dml_type']['DELETE'] ?? 0) }}</div>
</div>
</div>
<!-- 상위 테이블 + 파티션 현황 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<!-- 상위 테이블 -->
<div class="bg-white rounded-lg shadow-sm p-4">
<h3 class="text-sm font-semibold text-gray-700 mb-3">변경 빈도 상위 테이블</h3>
<div class="space-y-2">
@foreach($stats['top_tables'] as $table => $count)
<div class="flex justify-between items-center">
<a href="{{ route('trigger-audit.index', ['table_name' => $table]) }}"
class="text-sm text-blue-600 hover:underline">{{ $table }}</a>
<span class="text-sm text-gray-500">{{ number_format($count) }}</span>
</div>
@endforeach
</div>
</div>
<!-- 파티션 현황 -->
<div class="bg-white rounded-lg shadow-sm p-4">
<h3 class="text-sm font-semibold text-gray-700 mb-3">파티션 현황 (저장소: {{ $stats['storage_mb'] }}MB)</h3>
<div class="overflow-x-auto">
<table class="w-full text-xs">
<thead>
<tr class="text-gray-500">
<th class="text-left py-1">파티션</th>
<th class="text-right py-1"> </th>
</tr>
</thead>
<tbody>
@foreach($partitions as $p)
<tr class="border-t">
<td class="py-1">{{ $p->PARTITION_NAME }}</td>
<td class="text-right py-1">{{ number_format($p->TABLE_ROWS) }}</td>
</tr>
@endforeach
</tbody>
</table>
</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="table_name" onchange="this.form.submit()" class="border rounded-lg px-3 py-2 text-sm">
<option value="">전체</option>
@foreach($tables as $table => $cnt)
<option value="{{ $table }}" {{ request('table_name') === $table ? 'selected' : '' }}>{{ $table }} ({{ number_format($cnt) }})</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">DML 타입</label>
<select name="dml_type" onchange="this.form.submit()" class="border rounded-lg px-3 py-2 text-sm">
<option value="">전체</option>
@foreach(['INSERT', 'UPDATE', 'DELETE'] as $type)
<option value="{{ $type }}" {{ request('dml_type') === $type ? 'selected' : '' }}>{{ $type }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Row ID</label>
<input type="text" name="row_id" value="{{ request('row_id') }}" placeholder="레코드 ID"
class="border rounded-lg px-3 py-2 text-sm w-32">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">시작일</label>
<input type="date" name="from" value="{{ request('from') }}" class="border rounded-lg px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">종료일</label>
<input type="date" name="to" value="{{ request('to') }}" class="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>
</div>
<div>
<a href="{{ route('trigger-audit.index') }}" class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg text-sm inline-block">초기화</a>
</div>
</form>
</div>
<!-- 로그 테이블 -->
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
<table class="w-full text-sm">
<thead class="bg-gray-50 text-gray-600">
<tr>
<th class="px-4 py-3 text-left">ID</th>
<th class="px-4 py-3 text-left">테이블</th>
<th class="px-4 py-3 text-left">Row ID</th>
<th class="px-4 py-3 text-center">DML</th>
<th class="px-4 py-3 text-left">변경 컬럼</th>
<th class="px-4 py-3 text-left">DB 사용자</th>
<th class="px-4 py-3 text-left">일시</th>
<th class="px-4 py-3 text-center">액션</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@forelse($logs as $log)
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 text-gray-500">{{ $log->id }}</td>
<td class="px-4 py-3">
<a href="{{ route('trigger-audit.index', ['table_name' => $log->table_name]) }}"
class="text-blue-600 hover:underline">{{ $log->table_name }}</a>
</td>
<td class="px-4 py-3">
<a href="{{ route('trigger-audit.history', [$log->table_name, $log->row_id]) }}"
class="text-blue-600 hover:underline">{{ $log->row_id }}</a>
</td>
<td class="px-4 py-3 text-center">
<span class="px-2 py-1 rounded text-xs font-medium
{{ $log->dml_type === 'INSERT' ? 'bg-green-100 text-green-700' : '' }}
{{ $log->dml_type === 'UPDATE' ? 'bg-yellow-100 text-yellow-700' : '' }}
{{ $log->dml_type === 'DELETE' ? 'bg-red-100 text-red-700' : '' }}">
{{ $log->dml_type }}
</span>
</td>
<td class="px-4 py-3 text-gray-600 text-xs">
@if($log->changed_columns)
{{ implode(', ', array_slice($log->changed_columns, 0, 3)) }}
@if(count($log->changed_columns) > 3)
<span class="text-gray-400">+{{ count($log->changed_columns) - 3 }}</span>
@endif
@else
<span class="text-gray-400">-</span>
@endif
</td>
<td class="px-4 py-3 text-gray-500 text-xs">{{ $log->db_user ?? '-' }}</td>
<td class="px-4 py-3 text-gray-500 text-xs">{{ $log->created_at->format('m/d H:i:s') }}</td>
<td class="px-4 py-3 text-center">
<a href="{{ route('trigger-audit.show', $log->id) }}"
class="text-blue-600 hover:underline text-xs">상세</a>
</td>
</tr>
@empty
<tr>
<td colspan="8" class="px-4 py-8 text-center text-gray-400">감사 로그가 없습니다.</td>
</tr>
@endforelse
</tbody>
</table>
<!-- 페이지네이션 -->
<div class="px-4 py-3 bg-gray-50 border-t">
{{ $logs->links() }}
</div>
</div>
@endsection