${s.tagCount} tags • ${s.samplingMs}ms • ${formatDuration(s.durationSec)}
${formatDateTime(s.startedAt)} ${s.pinned ? '📌' : ''} `; item.onclick = () => fastSelect(s.id); list.appendChild(item); }); } async function fastStart() { const name = document.getElementById('fast-session-name').value.trim(); if (!name) { alert('세션 이름을 입력하세요.'); return; } const select = document.getElementById('fast-tag-select'); const tags = Array.from(select.selectedOptions).map(o => o.value); if (tags.length === 0) { alert('태그를 최소 1개 이상 선택하세요.'); return; } if (tags.length > 8) { alert('태그는 최대 8개까지 선택 가능합니다.'); return; } const samplingMs = parseInt(document.getElementById('fast-sampling-ms').value); const durationSec = parseInt(document.getElementById('fast-duration-sec').value); const retentionDays = document.getElementById('fast-retention-days').value.trim() || null; const res = await fetch('/api/fast/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, samplingMs, durationSec, tagList: tags, retentionDays }) }); if (!res.ok) { const err = await res.json(); alert('오류: ' + (err.error || '알 수 없는 오류')); return; } const data = await res.json(); await fastSessionsLoad(); fastSelect(data.id); document.getElementById('modal-fast-new').querySelector('.btn-close').click(); } async function fastStop(id) { const res = await fetch(`/api/fast/${id}/stop`, { method: 'POST' }); if (!res.ok) { alert('중지 실패'); return; } await fastSessionsLoad(); if (fastCurrentSessionId === id) { fastCurrentSessionId = null; fastClearChart(); } } async function fastDelete(id) { if (!confirm('세션을 삭제하시겠습니까?')) return; const res = await fetch(`/api/fast/${id}`, { method: 'DELETE' }); if (!res.ok) { alert('삭제 실패'); return; } await fastSessionsLoad(); if (fastCurrentSessionId === id) { fastCurrentSessionId = null; fastClearChart(); } } async function fastPin(id) { const res = await fetch(`/api/fast/${id}/pin`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pinned: true }) }); if (!res.ok) { alert('고정 실패'); return; } await fastSessionsLoad(); } async function fastSelect(id) { fastCurrentSessionId = id; const res = await fetch(`/api/fast/${id}`); if (!res.ok) { alert('세션 조회 실패'); return; } const session = await res.json(); document.getElementById('fast-session-title').textContent = `${session.name} (${session.status})`; document.getElementById('fast-progress-bar').style.width = '0%'; document.getElementById('fast-progress-text').textContent = '0 / 0 (0%)'; // 버튼 상태 업데이트 const isRunning = session.status === 'Running'; document.getElementById('btn-fast-stop').style.display = isRunning ? 'inline' : 'none'; document.getElementById('btn-fast-export-xlsx').style.display = isRunning ? 'none' : 'inline'; document.getElementById('btn-fast-export-csv').style.display = isRunning ? 'none' : 'inline'; document.getElementById('btn-fast-delete').style.display = 'inline'; document.getElementById('btn-fast-pin').textContent = session.pinned ? '고정 해제' : '고정'; // 태그 목록 업데이트 fastTagList = session.tagList; // 그래프 렌더링 await fastRenderChart(); // 라이브 폴링 시작 if (isRunning) { fastLivePollStart(); } else { fastLivePollStop(); } } async function fastRenderChart() { if (!fastCurrentSessionId) return; const res = await fetch(`/api/fast/${fastCurrentSessionId}/records`); if (!res.ok) return; const data = await res.json(); if (!data.items || data.items.length === 0) { document.getElementById('fast-chart-container').innerHTML = '