- 모델: AdminApiFlow, AdminApiFlowRun - 컨트롤러: FlowTesterController - 뷰: index, create, edit, history, run-detail - 사이드바 메뉴에 "개발 도구" 그룹 추가 - 라우트 설정
219 lines
12 KiB
PHP
219 lines
12 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>
|
|
<a href="{{ route('dev-tools.flow-tester.create') }}"
|
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition flex items-center gap-2">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
</svg>
|
|
새 플로우
|
|
</a>
|
|
</div>
|
|
|
|
<!-- 필터 영역 -->
|
|
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
|
|
<form method="GET" class="flex gap-4">
|
|
<!-- 카테고리 선택 -->
|
|
<div class="w-40">
|
|
<select name="category"
|
|
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
<option value="">전체 카테고리</option>
|
|
<option value="item-master" {{ request('category') === 'item-master' ? 'selected' : '' }}>item-master</option>
|
|
<option value="auth" {{ request('category') === 'auth' ? 'selected' : '' }}>auth</option>
|
|
<option value="bom" {{ request('category') === 'bom' ? 'selected' : '' }}>bom</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- 검색 -->
|
|
<div class="flex-1">
|
|
<input type="text"
|
|
name="search"
|
|
value="{{ request('search') }}"
|
|
placeholder="플로우 이름으로 검색..."
|
|
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
|
|
<!-- 검색 버튼 -->
|
|
<button type="submit" class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-2 rounded-lg transition">
|
|
검색
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- 플로우 목록 -->
|
|
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
|
@if($flows->isEmpty())
|
|
<div class="text-center py-12 text-gray-500">
|
|
<svg class="w-16 h-16 mx-auto mb-4 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
</svg>
|
|
<p class="text-lg font-medium">등록된 플로우가 없습니다</p>
|
|
<p class="text-sm mt-1">새 플로우를 생성하여 API 테스트를 시작하세요.</p>
|
|
</div>
|
|
@else
|
|
<table class="w-full">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">이름</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">카테고리</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">스텝</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">최근 실행</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">상태</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">액션</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200">
|
|
@foreach($flows as $flow)
|
|
@php
|
|
$latestRun = $flow->runs->first();
|
|
@endphp
|
|
<tr class="hover:bg-gray-50">
|
|
<td class="px-6 py-4">
|
|
<div class="font-medium text-gray-900">{{ $flow->name }}</div>
|
|
@if($flow->description)
|
|
<div class="text-sm text-gray-500 truncate max-w-xs">{{ $flow->description }}</div>
|
|
@endif
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
@if($flow->category)
|
|
<span class="px-2 py-1 text-xs font-medium bg-gray-100 text-gray-700 rounded">{{ $flow->category }}</span>
|
|
@else
|
|
<span class="text-gray-400">-</span>
|
|
@endif
|
|
</td>
|
|
<td class="px-6 py-4 text-sm text-gray-500">
|
|
{{ $flow->step_count }}개
|
|
</td>
|
|
<td class="px-6 py-4 text-sm text-gray-500">
|
|
@if($latestRun)
|
|
{{ $latestRun->created_at->diffForHumans() }}
|
|
@else
|
|
<span class="text-gray-400">-</span>
|
|
@endif
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
@if($latestRun)
|
|
<span class="px-2 py-1 text-xs font-medium rounded {{ $latestRun->status_color }}">
|
|
{{ $latestRun->status_label }}
|
|
</span>
|
|
@else
|
|
<span class="px-2 py-1 text-xs font-medium bg-gray-100 text-gray-500 rounded">대기</span>
|
|
@endif
|
|
</td>
|
|
<td class="px-6 py-4 text-right">
|
|
<div class="flex justify-end gap-2">
|
|
<!-- 실행 버튼 -->
|
|
<button onclick="runFlow({{ $flow->id }})"
|
|
class="p-2 text-green-600 hover:bg-green-50 rounded-lg transition"
|
|
title="실행">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</button>
|
|
<!-- 편집 버튼 -->
|
|
<a href="{{ route('dev-tools.flow-tester.edit', $flow->id) }}"
|
|
class="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition"
|
|
title="편집">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
</svg>
|
|
</a>
|
|
<!-- 이력 버튼 -->
|
|
<a href="{{ route('dev-tools.flow-tester.history', $flow->id) }}"
|
|
class="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition"
|
|
title="실행 이력">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</a>
|
|
<!-- 복제 버튼 -->
|
|
<form action="{{ route('dev-tools.flow-tester.clone', $flow->id) }}" method="POST" class="inline">
|
|
@csrf
|
|
<button type="submit"
|
|
class="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition"
|
|
title="복제">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
|
</svg>
|
|
</button>
|
|
</form>
|
|
<!-- 삭제 버튼 -->
|
|
<button onclick="confirmDelete({{ $flow->id }}, '{{ $flow->name }}')"
|
|
class="p-2 text-red-600 hover:bg-red-50 rounded-lg transition"
|
|
title="삭제">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- 페이지네이션 -->
|
|
@if($flows->hasPages())
|
|
<div class="px-6 py-4 border-t border-gray-200">
|
|
{{ $flows->links() }}
|
|
</div>
|
|
@endif
|
|
@endif
|
|
</div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script>
|
|
function runFlow(id) {
|
|
if (!confirm('이 플로우를 실행하시겠습니까?')) return;
|
|
|
|
fetch(`/dev-tools/flow-tester/${id}/run`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert(data.message);
|
|
location.reload();
|
|
} else {
|
|
alert('실행 실패: ' + (data.message || '알 수 없는 오류'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert('오류 발생: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function confirmDelete(id, name) {
|
|
if (!confirm(`"${name}" 플로우를 삭제하시겠습니까?`)) return;
|
|
|
|
fetch(`/dev-tools/flow-tester/${id}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
},
|
|
})
|
|
.then(response => {
|
|
if (response.ok) {
|
|
location.reload();
|
|
} else {
|
|
alert('삭제 실패');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert('오류 발생: ' + error.message);
|
|
});
|
|
}
|
|
</script>
|
|
@endpush
|