Files
sam-manage/resources/views/dev-tools/flow-tester/run-detail.blade.php
hskwon 5c892c1ed9 브라우저 alert/confirm을 SweetAlert2로 전환
- layouts/app.blade.php에 SweetAlert2 CDN 및 전역 헬퍼 함수 추가
  - showToast(): 토스트 알림 (success, error, warning, info)
  - showConfirm(): 확인 대화상자
  - showDeleteConfirm(): 삭제 확인 (경고 아이콘)
  - showPermanentDeleteConfirm(): 영구 삭제 확인 (빨간색 경고)
  - showSuccess(), showError(): 성공/에러 알림

- 변환된 파일 목록 (48개 Blade 파일):
  - menus/* (6개), boards/* (2개), posts/* (3개)
  - daily-logs/* (3개), project-management/* (6개)
  - dev-tools/flow-tester/* (6개)
  - quote-formulas/* (4개), permission-analyze/* (1개)
  - archived-records/* (1개), profile/* (1개)
  - roles/* (3개), permissions/* (3개)
  - departments/* (3개), tenants/* (3개), users/* (3개)

- 주요 개선사항:
  - Tailwind CSS 테마와 일관된 디자인
  - 비동기 콜백 패턴으로 리팩토링
  - 삭제/복원/영구삭제 각각 다른 스타일 적용
2025-12-05 09:49:56 +09:00

532 lines
36 KiB
PHP

@extends('layouts.app')
@section('title', '실행 상세 - #' . $run->id)
@section('content')
<!-- 페이지 헤더 -->
<div class="flex justify-between items-center mb-6">
<div class="flex items-center gap-4">
<a href="{{ route('dev-tools.flow-tester.history', $run->flow_id) }}"
class="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
</a>
<div>
<h1 class="text-2xl font-bold text-gray-800">실행 상세 #{{ $run->id }}</h1>
<p class="text-sm text-gray-500">{{ $run->flow->name }}</p>
</div>
</div>
<div class="flex items-center gap-3">
@if($run->status === 'FAILED' || $run->status === 'PARTIAL')
<!-- AI에게 오류 복사 버튼 -->
<button onclick="copyErrorForAI()"
id="copy-error-btn"
class="bg-purple-600 hover:bg-purple-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="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
</svg>
AI에게 복사
</button>
@endif
<!-- 다시 실행 버튼 -->
<button onclick="runFlow({{ $run->flow_id }})"
id="run-btn"
class="bg-green-600 hover:bg-green-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="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', $run->flow_id) }}"
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="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>
<span class="px-3 py-1 text-sm font-medium rounded {{ $run->status_color }}">
{{ $run->status_label }}
</span>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- 요약 정보 -->
<div class="lg:col-span-1">
<div class="bg-white rounded-lg shadow-sm p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">실행 정보</h2>
<dl class="space-y-3">
<div class="flex justify-between">
<dt class="text-gray-500">상태</dt>
<dd>
<span class="px-2 py-1 text-xs font-medium rounded {{ $run->status_color }}">
{{ $run->status_label }}
</span>
</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500">진행률</dt>
<dd class="text-gray-900">{{ $run->progress }}%</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500">완료 스텝</dt>
<dd class="text-gray-900">{{ $run->completed_steps }}/{{ $run->total_steps ?? '-' }}</dd>
</div>
@if($run->failed_step)
<div class="flex justify-between">
<dt class="text-gray-500">실패 스텝</dt>
<dd class="text-red-600">Step {{ $run->failed_step }}</dd>
</div>
@endif
<div class="flex justify-between">
<dt class="text-gray-500">소요시간</dt>
<dd class="text-gray-900">
@if($run->duration_ms)
{{ number_format($run->duration_ms / 1000, 2) }}s
@else
-
@endif
</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500">시작시간</dt>
<dd class="text-gray-900">{{ $run->started_at?->format('H:i:s') ?? '-' }}</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500">완료시간</dt>
<dd class="text-gray-900">{{ $run->completed_at?->format('H:i:s') ?? '-' }}</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500">실행일</dt>
<dd class="text-gray-900">{{ $run->created_at->format('Y-m-d') }}</dd>
</div>
</dl>
</div>
@if($run->error_message)
<div class="mt-4 bg-red-50 border border-red-200 rounded-lg p-4">
<h3 class="text-sm font-medium text-red-800 mb-2">에러 메시지</h3>
<p class="text-sm text-red-700">{{ $run->error_message }}</p>
</div>
@endif
</div>
<!-- 실행 로그 -->
<div class="lg:col-span-2">
<div class="bg-white rounded-lg shadow-sm">
<!-- 헤더 -->
<div class="px-6 py-4 border-b">
<h2 class="text-lg font-semibold text-gray-800">실행 로그</h2>
</div>
<!-- 스텝 카드들 -->
<div class="p-6">
@php
// 전체 스텝 목록 (flow_definition에서)
$allSteps = $run->flow->flow_definition['steps'] ?? [];
// execution_log를 stepId로 인덱싱
$executionByStepId = [];
foreach ($run->execution_log ?? [] as $log) {
$stepId = $log['stepId'] ?? $log['step_id'] ?? null;
if ($stepId) $executionByStepId[$stepId] = $log;
}
// api_logs를 URI 기준으로 그룹화
$apiLogsByUri = [];
foreach ($run->api_logs ?? [] as $apiLog) {
$uri = $apiLog['uri'] ?? '';
if ($uri) {
$apiLogsByUri[$uri][] = $apiLog;
}
}
@endphp
@if(count($allSteps) > 0)
<div class="space-y-3">
@foreach($allSteps as $index => $step)
@php
$stepId = $step['id'] ?? 'step_'.($index + 1);
$stepName = $step['name'] ?? '';
$stepMethod = $step['method'] ?? '';
$stepEndpoint = $step['endpoint'] ?? '';
$executed = $executionByStepId[$stepId] ?? null;
// null = 미실행, true = 성공, false = 실패
$status = $executed ? ($executed['success'] ?? false) : null;
// 실패 스텝의 api_logs 찾기
$stepApiLogs = [];
if ($status === false && $stepEndpoint) {
foreach ($apiLogsByUri as $uri => $logs) {
if (str_contains($uri, $stepEndpoint) || str_contains($stepEndpoint, $uri)) {
$stepApiLogs = array_merge($stepApiLogs, $logs);
}
}
}
@endphp
{{-- 미실행 카드 (회색) --}}
@if($status === null)
<div class="border rounded-lg border-gray-200 bg-gray-50 p-4 opacity-60">
<div class="flex items-center gap-2">
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2" />
</svg>
<span class="font-medium text-gray-500">{{ $stepId }}</span>
<span class="text-gray-400">{{ $stepName }}</span>
<code class="text-xs text-gray-400 bg-white bg-opacity-50 px-2 py-0.5 rounded">
{{ $stepMethod }} {{ Str::limit($stepEndpoint, 30) }}
</code>
<span class="ml-auto text-xs text-gray-400">(미실행)</span>
</div>
</div>
{{-- 성공 카드 (녹색, 항상 펼쳐짐) --}}
@elseif($status === true)
<div class="border rounded-lg border-green-200 bg-green-50">
{{-- 헤더 --}}
<div class="flex justify-between items-center p-4">
<div class="flex items-center gap-2">
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
<span class="font-medium">{{ $stepId }}</span>
<span class="text-gray-500">{{ $executed['name'] ?? $executed['stepName'] ?? $stepName }}</span>
<code class="text-xs text-gray-600 bg-white bg-opacity-50 px-2 py-0.5 rounded">
{{ $executed['request']['method'] ?? $stepMethod }} {{ Str::limit($executed['request']['endpoint'] ?? $stepEndpoint, 30) }}
</code>
</div>
<div class="flex items-center gap-3">
@if(isset($executed['response']['status']))
<span class="px-2 py-0.5 text-xs font-medium rounded bg-green-600 text-white">
{{ $executed['response']['status'] }}
</span>
@endif
<span class="text-sm text-gray-500">{{ $executed['duration'] ?? '' }}ms</span>
</div>
</div>
{{-- 상세 (항상 표시) --}}
<div class="px-4 pb-4 border-t border-green-200">
<div class="pt-3 space-y-3">
@if(isset($executed['request']))
<div class="text-sm">
<div class="font-medium text-gray-700 mb-1">Request</div>
<div class="bg-white bg-opacity-60 rounded p-2">
<code class="text-gray-800">{{ $executed['request']['method'] ?? '' }} {{ $executed['request']['endpoint'] ?? '' }}</code>
@if(!empty($executed['request']['body']))
<details class="mt-2">
<summary class="cursor-pointer text-xs text-gray-500 hover:text-gray-700">요청 데이터</summary>
<pre class="mt-1 p-2 bg-gray-100 rounded text-xs overflow-x-auto">{{ json_encode($executed['request']['body'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
</details>
@endif
</div>
</div>
@endif
@if(isset($executed['response']))
<div class="text-sm">
<div class="font-medium text-gray-700 mb-1">Response</div>
<div class="bg-white bg-opacity-60 rounded p-2">
<span class="px-2 py-0.5 text-xs font-medium rounded bg-green-100 text-green-700">{{ $executed['response']['status'] ?? '-' }}</span>
@if(!empty($executed['response']['body']))
<details class="mt-2">
<summary class="cursor-pointer text-xs text-gray-500 hover:text-gray-700">응답 데이터</summary>
<pre class="mt-1 p-2 bg-gray-100 rounded text-xs overflow-x-auto max-h-60 overflow-y-auto">{{ json_encode($executed['response']['body'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
</details>
@endif
</div>
</div>
@endif
@if(isset($executed['reason']))
<div class="text-sm text-green-700 bg-green-100 px-3 py-2 rounded">
<span class="font-medium">성공 이유:</span> {{ $executed['reason'] }}
</div>
@endif
</div>
</div>
</div>
{{-- 실패 카드 (빨간색, 항상 펼쳐짐 + Laravel Log 버튼) --}}
@else
<div class="border rounded-lg border-red-200 bg-red-50">
{{-- 헤더 --}}
<div class="flex justify-between items-center p-4">
<div class="flex items-center gap-2">
<svg class="w-5 h-5 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
<span class="font-medium">{{ $stepId }}</span>
<span class="text-gray-500">{{ $executed['name'] ?? $executed['stepName'] ?? $stepName }}</span>
<code class="text-xs text-gray-600 bg-white bg-opacity-50 px-2 py-0.5 rounded">
{{ $executed['request']['method'] ?? $stepMethod }} {{ Str::limit($executed['request']['endpoint'] ?? $stepEndpoint, 30) }}
</code>
</div>
<div class="flex items-center gap-3">
@if(isset($executed['response']['status']))
<span class="px-2 py-0.5 text-xs font-medium rounded bg-red-600 text-white">
{{ $executed['response']['status'] }}
</span>
@endif
<span class="text-sm text-gray-500">{{ $executed['duration'] ?? '' }}ms</span>
</div>
</div>
{{-- 상세 (항상 표시) --}}
<div class="px-4 pb-4 border-t border-red-200">
<div class="pt-3 space-y-3">
@if(isset($executed['request']))
<div class="text-sm">
<div class="font-medium text-gray-700 mb-1">Request</div>
<div class="bg-white bg-opacity-60 rounded p-2">
<code class="text-gray-800">{{ $executed['request']['method'] ?? '' }} {{ $executed['request']['endpoint'] ?? '' }}</code>
@if(!empty($executed['request']['body']))
<details class="mt-2">
<summary class="cursor-pointer text-xs text-gray-500 hover:text-gray-700">요청 데이터</summary>
<pre class="mt-1 p-2 bg-gray-100 rounded text-xs overflow-x-auto">{{ json_encode($executed['request']['body'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
</details>
@endif
</div>
</div>
@endif
@if(isset($executed['response']))
<div class="text-sm">
<div class="font-medium text-gray-700 mb-1">Response</div>
<div class="bg-white bg-opacity-60 rounded p-2">
<div class="flex items-center gap-2">
<span class="px-2 py-0.5 text-xs font-medium rounded bg-red-100 text-red-700">{{ $executed['response']['status'] ?? '-' }}</span>
@if(isset($executed['expect']['status']))
<span class="text-xs text-gray-500">
예상: {{ is_array($executed['expect']['status']) ? implode(', ', $executed['expect']['status']) : $executed['expect']['status'] }}
</span>
@endif
</div>
@if(!empty($executed['response']['body']))
<details class="mt-2">
<summary class="cursor-pointer text-xs text-gray-500 hover:text-gray-700">응답 데이터</summary>
<pre class="mt-1 p-2 bg-gray-100 rounded text-xs overflow-x-auto max-h-60 overflow-y-auto">{{ json_encode($executed['response']['body'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
</details>
@endif
</div>
</div>
@endif
@if(isset($executed['reason']))
<div class="text-sm text-red-700 bg-red-100 px-3 py-2 rounded">
<span class="font-medium">실패 이유:</span> {{ $executed['reason'] }}
</div>
@endif
@if(isset($executed['error']))
<div class="text-sm bg-red-100 rounded p-3">
<div class="font-medium text-red-800 mb-1">에러</div>
<div class="text-red-700">{{ $executed['error'] }}</div>
</div>
@endif
{{-- Laravel Log 보기 (DaisyUI collapse) --}}
@if(!empty($stepApiLogs))
<div class="pt-2 border-t border-red-200">
<details class="collapse collapse-arrow bg-purple-50 rounded-lg">
<summary class="collapse-title text-sm text-purple-600 font-medium min-h-0 py-2 px-3 flex items-center gap-2">
Laravel Log 보기 (API 서버)
<span class="px-1.5 py-0.5 text-xs bg-purple-100 text-purple-700 rounded">{{ count($stepApiLogs) }}</span>
</summary>
<div class="collapse-content px-3 pb-3">
<div class="space-y-2 pt-2">
@foreach($stepApiLogs as $apiLog)
@php
$isReq = ($apiLog['type'] ?? '') === 'request';
$isErr = !$isReq && (($apiLog['status'] ?? 0) >= 400);
@endphp
<div class="bg-white rounded p-3 border {{ $isErr ? 'border-red-300' : 'border-gray-200' }}">
<div class="flex items-center gap-2 mb-2">
<span class="px-2 py-0.5 text-xs font-medium rounded {{ $isReq ? 'bg-blue-100 text-blue-700' : ($isErr ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700') }}">
{{ $isReq ? 'REQ' : 'RES' }}
</span>
@if($isReq)
<code class="text-xs text-gray-700">{{ $apiLog['method'] ?? '' }} {{ $apiLog['uri'] ?? '' }}</code>
@else
<span class="px-2 py-0.5 text-xs font-medium rounded {{ $isErr ? 'bg-red-600 text-white' : 'bg-green-600 text-white' }}">
{{ $apiLog['status'] ?? '-' }}
</span>
@endif
<span class="text-xs text-gray-400 ml-auto">{{ $apiLog['timestamp'] ?? '' }}</span>
</div>
{{-- 원본 로그 표시 --}}
@if(!empty($apiLog['raw']))
<pre class="p-2 bg-gray-900 text-gray-100 rounded text-xs overflow-x-auto whitespace-pre-wrap break-all font-mono">{{ $apiLog['raw'] }}</pre>
@else
{{-- raw가 없는 경우 기존 방식 --}}
@if($isReq && !empty($apiLog['input']))
<details class="mt-1">
<summary class="cursor-pointer text-xs text-gray-500 hover:text-gray-700">요청 데이터</summary>
<pre class="mt-1 p-2 bg-gray-100 rounded text-xs overflow-x-auto max-h-32">{{ json_encode($apiLog['input'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
</details>
@endif
@if(!$isReq && isset($apiLog['message']))
<div class="text-xs {{ $isErr ? 'text-red-600' : 'text-green-600' }}">{{ $apiLog['message'] }}</div>
@endif
@if(!$isReq && isset($apiLog['error']))
<details class="mt-1" open>
<summary class="cursor-pointer text-xs text-red-600 font-medium">오류 상세</summary>
<pre class="mt-1 p-2 bg-red-50 rounded text-xs overflow-x-auto max-h-40 text-red-800">{{ is_array($apiLog['error']) ? json_encode($apiLog['error'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) : $apiLog['error'] }}</pre>
</details>
@endif
@endif
</div>
@endforeach
</div>
</div>
</details>
</div>
@endif
</div>
</div>
</div>
@endif
@endforeach
</div>
@else
<div class="text-center py-8 text-gray-500">
<p>스텝 정보가 없습니다.</p>
</div>
@endif
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
// AI에게 오류 정보 복사
function copyErrorForAI() {
const errorData = {
url: window.location.href,
flowName: @json($run->flow->name),
runId: {{ $run->id }},
status: @json($run->status),
statusLabel: @json($run->status_label),
errorMessage: @json($run->error_message),
failedStep: @json($run->failed_step),
completedSteps: {{ $run->completed_steps }},
totalSteps: {{ $run->total_steps ?? 0 }},
executionLog: @json($run->execution_log),
};
// 실패한 스텝들 추출
const failedSteps = (errorData.executionLog || [])
.filter(log => !log.success)
.map(log => ({
stepId: log.step_id || 'Unknown',
name: log.name || '',
reason: log.reason || '',
error: log.error || '',
request: log.request || null,
response: log.response || null,
}));
// 마크다운 포맷 생성
let markdown = `## Flow Tester 오류 보고서\n\n`;
markdown += `**URL:** ${errorData.url}\n`;
markdown += `**플로우:** ${errorData.flowName}\n`;
markdown += `**실행 ID:** #${errorData.runId}\n`;
markdown += `**상태:** ${errorData.statusLabel} (${errorData.status})\n`;
markdown += `**진행률:** ${errorData.completedSteps}/${errorData.totalSteps} 스텝 완료\n`;
if (errorData.failedStep) {
markdown += `**실패 스텝:** Step ${errorData.failedStep}\n`;
}
if (errorData.errorMessage) {
markdown += `\n### 에러 메시지\n\`\`\`\n${errorData.errorMessage}\n\`\`\`\n`;
}
if (failedSteps.length > 0) {
markdown += `\n### 실패한 스텝 상세\n`;
failedSteps.forEach((step, index) => {
markdown += `\n#### ${index + 1}. ${step.stepId} - ${step.name}\n`;
if (step.reason) {
markdown += `- **실패 이유:** ${step.reason}\n`;
}
if (step.error) {
markdown += `- **에러:** ${step.error}\n`;
}
if (step.request) {
markdown += `- **요청:** \`${step.request.method || ''} ${step.request.endpoint || ''}\`\n`;
if (step.request.body && Object.keys(step.request.body).length > 0) {
markdown += `- **요청 데이터:**\n\`\`\`json\n${JSON.stringify(step.request.body, null, 2)}\n\`\`\`\n`;
}
}
if (step.response) {
markdown += `- **응답 상태:** ${step.response.status || '-'}\n`;
if (step.response.body) {
markdown += `- **응답 데이터:**\n\`\`\`json\n${JSON.stringify(step.response.body, null, 2)}\n\`\`\`\n`;
}
}
});
}
markdown += `\n---\n*이 오류를 분석하고 해결 방법을 제안해주세요.*\n`;
// 클립보드에 복사
navigator.clipboard.writeText(markdown).then(() => {
const btn = document.getElementById('copy-error-btn');
const originalHtml = btn.innerHTML;
btn.innerHTML = `<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="M5 13l4 4L19 7" />
</svg> 복사 완료!`;
btn.classList.remove('bg-purple-600', 'hover:bg-purple-700');
btn.classList.add('bg-green-600');
setTimeout(() => {
btn.innerHTML = originalHtml;
btn.classList.remove('bg-green-600');
btn.classList.add('bg-purple-600', 'hover:bg-purple-700');
}, 2000);
}).catch(err => {
showToast('복사 실패: ' + err.message, 'error');
});
}
function runFlow(id) {
showConfirm('이 플로우를 다시 실행하시겠습니까?', () => {
const btn = document.getElementById('run-btn');
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = `<svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg> 실행 중...`;
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.run_id) {
window.location.href = `/dev-tools/flow-tester/runs/${data.run_id}`;
} else {
btn.disabled = false;
btn.innerHTML = originalHtml;
showToast(data.message || '실행 완료', 'success');
location.reload();
}
})
.catch(error => {
btn.disabled = false;
btn.innerHTML = originalHtml;
showToast('오류 발생: ' + error.message, 'error');
});
}, { title: '플로우 재실행', icon: 'question' });
}
</script>
@endpush