- operation 상세 페이지 및 일괄 롤백 실행 기능 추가
- TriggerAuditLog에 scopeForOperation 스코프 추가
- 트리거 INSERT/UPDATE/DELETE에 operation_id 컬럼 포함
- 감사로그 목록에 작업 단위 링크 컬럼 추가
- 라우트: operation/{id}, batch-rollback 추가
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
174 lines
7.8 KiB
PHP
174 lines
7.8 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', '작업 단위 상세')
|
|
|
|
@section('content')
|
|
<div class="flex justify-between items-center mb-6">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-800">작업 단위 상세</h1>
|
|
<p class="text-sm text-gray-500 mt-1">
|
|
Operation ID: <span class="font-mono">{{ $summary['operation_id'] }}</span>
|
|
</p>
|
|
</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">목록</a>
|
|
</div>
|
|
|
|
@if(session('success'))
|
|
<div class="bg-green-100 text-green-700 px-4 py-3 rounded-lg mb-6">{{ session('success') }}</div>
|
|
@endif
|
|
@if(session('error'))
|
|
<div class="bg-red-100 text-red-700 px-4 py-3 rounded-lg mb-6">{{ session('error') }}</div>
|
|
@endif
|
|
|
|
<!-- 요약 정보 -->
|
|
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">작업 요약</h3>
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
<div>
|
|
<span class="text-gray-500">총 변경 건수</span>
|
|
<div class="mt-1 text-2xl font-bold text-blue-600">{{ $summary['total_count'] }}건</div>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-500">영향받은 테이블</span>
|
|
<div class="mt-1">
|
|
@foreach($summary['tables'] as $table => $count)
|
|
<span class="inline-block bg-gray-100 text-gray-700 px-2 py-0.5 rounded text-xs mr-1 mb-1">
|
|
{{ $table }} ({{ $count }})
|
|
</span>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-500">DML 유형</span>
|
|
<div class="mt-1">
|
|
@foreach($summary['dml_types'] as $type => $count)
|
|
<span class="inline-block px-2 py-0.5 rounded text-xs mr-1 mb-1
|
|
{{ $type === 'INSERT' ? 'bg-green-100 text-green-700' : '' }}
|
|
{{ $type === 'UPDATE' ? 'bg-yellow-100 text-yellow-700' : '' }}
|
|
{{ $type === 'DELETE' ? 'bg-red-100 text-red-700' : '' }}">
|
|
{{ $type }} ({{ $count }})
|
|
</span>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-500">시간 범위</span>
|
|
<div class="mt-1 text-xs font-medium">
|
|
{{ $summary['started_at']->format('Y-m-d H:i:s') }}
|
|
@if($summary['started_at'] != $summary['ended_at'])
|
|
<br>~ {{ $summary['ended_at']->format('H:i:s') }}
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@if($summary['actor_id'])
|
|
<div>
|
|
<span class="text-gray-500">Actor ID</span>
|
|
<div class="mt-1 font-medium">{{ $summary['actor_id'] }}</div>
|
|
</div>
|
|
@endif
|
|
@if($summary['session_info'])
|
|
<div>
|
|
<span class="text-gray-500">IP / Route</span>
|
|
<div class="mt-1 text-xs font-medium">
|
|
{{ $summary['session_info']['ip'] ?? '-' }} /
|
|
{{ $summary['session_info']['route'] ?? '-' }}
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 변경 내역 목록 (롤백 역순) -->
|
|
<div class="bg-white rounded-lg shadow-sm overflow-hidden mb-6">
|
|
<h3 class="text-sm font-semibold text-gray-700 px-4 py-3 bg-gray-50 border-b">
|
|
변경 내역 (롤백 실행 순서)
|
|
</h3>
|
|
<div class="divide-y divide-gray-100">
|
|
@foreach($rollbackItems as $idx => $item)
|
|
@php $log = $item['log']; @endphp
|
|
<div class="p-4">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<div class="flex items-center gap-3">
|
|
<span class="bg-gray-200 text-gray-600 text-xs font-bold rounded-full w-6 h-6 flex items-center justify-center">
|
|
{{ $loop->iteration }}
|
|
</span>
|
|
<span class="px-2 py-0.5 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>
|
|
<span class="text-sm font-medium text-gray-800">
|
|
{{ $log->table_name }}.{{ $log->row_id }}
|
|
</span>
|
|
<span class="text-xs text-gray-400">
|
|
#{{ $log->id }}
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-xs text-orange-600 font-medium">
|
|
@if($log->dml_type === 'INSERT') DELETE
|
|
@elseif($log->dml_type === 'UPDATE') REVERT
|
|
@elseif($log->dml_type === 'DELETE') RE-INSERT
|
|
@endif
|
|
</span>
|
|
<a href="{{ route('trigger-audit.show', $log->id) }}"
|
|
class="text-blue-500 hover:text-blue-700 text-xs">상세</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 롤백 SQL -->
|
|
<div class="bg-gray-900 text-green-400 p-3 rounded text-xs font-mono overflow-x-auto">
|
|
<pre class="whitespace-pre-wrap">{{ $item['sql'] }}</pre>
|
|
</div>
|
|
|
|
<!-- 변경 컬럼 (UPDATE인 경우) -->
|
|
@if($log->changed_columns)
|
|
<div class="mt-2">
|
|
@foreach($log->changed_columns as $col)
|
|
<span class="inline-block bg-yellow-100 text-yellow-700 px-2 py-0.5 rounded text-xs mr-1">{{ $col }}</span>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 경고 -->
|
|
<div class="bg-orange-50 border border-orange-200 rounded-lg p-4 mb-6">
|
|
<div class="flex items-start gap-3">
|
|
<span class="text-orange-500 text-xl">⚠</span>
|
|
<div>
|
|
<h4 class="text-sm font-semibold text-orange-700">일괄 롤백 주의사항</h4>
|
|
<ul class="text-sm text-orange-600 mt-1 space-y-1">
|
|
<li>위 {{ $summary['total_count'] }}건의 SQL이 하나의 트랜잭션으로 실행됩니다.</li>
|
|
<li>롤백은 역순(마지막 변경 → 첫 번째 변경)으로 실행됩니다.</li>
|
|
<li>롤백 이후 해당 레코드에 추가 변경이 있었으면 데이터 충돌이 발생할 수 있습니다.</li>
|
|
<li>실패 시 전체 트랜잭션이 롤백되어 데이터는 변경되지 않습니다.</li>
|
|
<li>운영 환경에서는 반드시 백업 후 실행하세요.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 일괄 롤백 실행 폼 -->
|
|
<div class="bg-white rounded-lg shadow-sm p-4">
|
|
<form method="POST" action="{{ route('trigger-audit.batch-rollback', $summary['operation_id']) }}"
|
|
onsubmit="return confirm('정말로 {{ $summary['total_count'] }}건의 일괄 롤백을 실행하시겠습니까?\n이 작업은 되돌릴 수 없습니다.')">
|
|
@csrf
|
|
<div class="flex items-center gap-4">
|
|
<label class="flex items-center gap-2 text-sm">
|
|
<input type="checkbox" name="confirm" value="1" required
|
|
class="rounded border-gray-300 text-orange-600 focus:ring-orange-500">
|
|
<span class="text-gray-700">위 {{ $summary['total_count'] }}건의 SQL을 실행하여 데이터를 일괄 롤백합니다.</span>
|
|
</label>
|
|
<button type="submit"
|
|
class="bg-orange-500 hover:bg-orange-600 text-white px-6 py-2 rounded-lg text-sm font-medium">
|
|
일괄 롤백 실행 ({{ $summary['total_count'] }}건)
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
@endsection |