document.addEventListener('DOMContentLoaded', () => { // ───────────────────────────────────────────────────────────── // 스크립트 선택 시 XML 드롭다운 토글 // ───────────────────────────────────────────────────────────── const TARGET_SCRIPT = "02-set_config.py"; const scriptSelect = document.getElementById('script'); const xmlGroup = document.getElementById('xmlFileGroup'); function toggleXml() { if (!scriptSelect || !xmlGroup) return; if (scriptSelect.value === TARGET_SCRIPT) { xmlGroup.style.display = 'block'; xmlGroup.classList.add('fade-in'); } else { xmlGroup.style.display = 'none'; } } if (scriptSelect) { toggleXml(); scriptSelect.addEventListener('change', toggleXml); } // ───────────────────────────────────────────────────────────── // 파일 보기 모달 // ───────────────────────────────────────────────────────────── const modalEl = document.getElementById('fileViewModal'); const titleEl = document.getElementById('fileViewModalLabel'); const contentEl = document.getElementById('fileViewContent'); if (modalEl) { modalEl.addEventListener('show.bs.modal', async (ev) => { const btn = ev.relatedTarget; const folder = btn?.getAttribute('data-folder') || ''; const date = btn?.getAttribute('data-date') || ''; const filename = btn?.getAttribute('data-filename') || ''; titleEl.innerHTML = `${filename || '파일'}`; contentEl.textContent = '불러오는 중...'; const params = new URLSearchParams(); if (folder) params.set('folder', folder); if (date) params.set('date', date); if (filename) params.set('filename', filename); try { const res = await fetch(`/view_file?${params.toString()}`, { cache: 'no-store' }); if (!res.ok) throw new Error('HTTP ' + res.status); const data = await res.json(); contentEl.textContent = data?.content ?? '(빈 파일)'; } catch (e) { contentEl.textContent = '파일을 불러오지 못했습니다: ' + (e?.message || e); } }); } // ───────────────────────────────────────────────────────────── // 진행바 업데이트 // ───────────────────────────────────────────────────────────── window.updateProgress = function (val) { const bar = document.getElementById('progressBar'); if (!bar) return; const v = Math.max(0, Math.min(100, Number(val) || 0)); bar.style.width = v + '%'; bar.setAttribute('aria-valuenow', v); bar.innerHTML = `${v}%`; }; // ───────────────────────────────────────────────────────────── // CSRF 토큰 // ───────────────────────────────────────────────────────────── const csrfToken = document.querySelector('input[name="csrf_token"]')?.value || ''; // ───────────────────────────────────────────────────────────── // IP 입력 데이터 보존 (Local Storage) // ───────────────────────────────────────────────────────────── const ipTextarea = document.getElementById('ips'); const ipForm = document.getElementById('ipForm'); const STORAGE_KEY_IP = 'ip_input_draft'; if (ipTextarea) { // 1. 페이지 로드 시 저장된 값 복원 const savedIps = localStorage.getItem(STORAGE_KEY_IP); if (savedIps) { ipTextarea.value = savedIps; // 라인 수 업데이트 트리거 if (window.updateIpCount) window.updateIpCount(); } // 2. 입력 시마다 저장 ipTextarea.addEventListener('input', () => { localStorage.setItem(STORAGE_KEY_IP, ipTextarea.value); // script.js에 있는 updateIpCount 호출 (있다면) if (window.updateIpCount) window.updateIpCount(); }); // 3. 폼 제출 성공 시 초기화? // 사용자의 의도에 따라 다름: "변경이 되지 않는 이상 계속 가지고 있게" // -> 제출 후에도 유지하는 것이 요청 사항에 부합함. // 만약 '성공적으로 작업이 끝나면 지워달라'는 요청이 있으면 여기를 수정. // 현재 요청: "페이지가 리셋이되도 변경이 되지 않는이상 계속 가지고있게" -> 유지. } // ───────────────────────────────────────────────────────────── // 공통 POST 함수 // ───────────────────────────────────────────────────────────── async function postFormAndHandle(url) { const res = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'X-CSRFToken': csrfToken, 'Accept': 'application/json, text/html;q=0.9,*/*;q=0.8', }, }); const ct = (res.headers.get('content-type') || '').toLowerCase(); if (ct.includes('application/json')) { const data = await res.json(); if (data.success === false) { throw new Error(data.error || ('HTTP ' + res.status)); } return data; } return { success: true, html: true }; } // ───────────────────────────────────────────────────────────── // MAC 파일 이동 // ───────────────────────────────────────────────────────────── const macForm = document.getElementById('macMoveForm'); if (macForm) { macForm.addEventListener('submit', async (e) => { e.preventDefault(); const btn = macForm.querySelector('button'); const originalHtml = btn.innerHTML; btn.disabled = true; btn.innerHTML = '처리 중...'; try { await postFormAndHandle(macForm.action); location.reload(); } catch (err) { alert('MAC 파일 이동 중 오류가 발생했습니다: ' + (err?.message || err)); btn.disabled = false; btn.innerHTML = originalHtml; } }); } // ───────────────────────────────────────────────────────────── // GUID 파일 이동 // ───────────────────────────────────────────────────────────── const guidForm = document.getElementById('guidMoveForm'); if (guidForm) { guidForm.addEventListener('submit', async (e) => { e.preventDefault(); const btn = guidForm.querySelector('button'); const originalHtml = btn.innerHTML; btn.disabled = true; btn.innerHTML = '처리 중...'; try { await postFormAndHandle(guidForm.action); location.reload(); } catch (err) { alert('GUID 파일 이동 중 오류가 발생했습니다: ' + (err?.message || err)); btn.disabled = false; btn.innerHTML = originalHtml; } }); } // ───────────────────────────────────────────────────────────── // 알림 자동 닫기 // ───────────────────────────────────────────────────────────── setTimeout(() => { document.querySelectorAll('.alert').forEach(alert => { const bsAlert = new bootstrap.Alert(alert); bsAlert.close(); }); }, 5000); // ───────────────────────────────────────────────────────────── // IP 스캔 로직 (Modal) // ───────────────────────────────────────────────────────────── const btnScan = document.getElementById('btnStartScan'); if (btnScan) { btnScan.addEventListener('click', async () => { const startIp = '10.10.0.2'; const endIp = '10.10.0.255'; const ipsTextarea = document.getElementById('ips'); const progressBar = document.getElementById('progressBar'); // UI 상태 변경 (로딩 중) const originalIcon = btnScan.innerHTML; btnScan.disabled = true; btnScan.innerHTML = ''; // 메인 진행바 활용 if (progressBar) { const progressContainer = progressBar.closest('.progress'); if (progressContainer) { progressContainer.parentElement.classList.remove('d-none'); } progressBar.style.width = '100%'; progressBar.classList.add('progress-bar-striped', 'progress-bar-animated'); progressBar.textContent = 'IP 스캔 중...'; } try { const res = await fetch('/utils/scan_network', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }, body: JSON.stringify({ start_ip: startIp, end_ip: endIp }) }); // 1. 세션 만료로 인한 리다이렉트 감지 if (res.redirected) { alert('세션이 만료되었습니다. 다시 로그인해주세요.'); window.location.reload(); return; } // 2. JSON 응답인지 확인 const contentType = res.headers.get("content-type"); if (!contentType || !contentType.includes("application/json")) { const text = await res.text(); if (text.includes("CSRF")) { throw new Error("보안 토큰(CSRF)이 만료되었습니다. 페이지를 새로고침해주세요."); } throw new Error(`서버 응답 오류 (HTTP ${res.status}): ${text.substring(0, 100)}...`); } const data = await res.json(); if (data.success) { if (data.active_ips && data.active_ips.length > 0) { ipsTextarea.value = data.active_ips.join('\n'); // 이벤트 트리거 ipsTextarea.dispatchEvent(new Event('input')); alert(`스캔 완료: ${data.active_ips.length}개의 활성 IP를 찾았습니다.`); } else { alert('활성 IP를 발견하지 못했습니다.'); } } else { throw new Error(data.error || 'Unknown error'); } } catch (err) { console.error(err); alert('오류가 발생했습니다: ' + (err.message || err)); } finally { // 상태 복구 btnScan.disabled = false; btnScan.innerHTML = originalIcon; if (progressBar) { // 진행바 초기화 progressBar.style.width = '0%'; progressBar.textContent = '0%'; progressBar.classList.remove('progress-bar-striped', 'progress-bar-animated'); } } }); } // ───────────────────────────────────────────────────────────── // IP 입력 지우기 버튼 // ───────────────────────────────────────────────────────────── const btnClear = document.getElementById('btnClearIps'); if (btnClear) { btnClear.addEventListener('click', () => { const ipsTextarea = document.getElementById('ips'); if (ipsTextarea) { ipsTextarea.value = ''; ipsTextarea.dispatchEvent(new Event('input')); // 로컬 스토리지 업데이트 및 카운트 갱신 트리거 } }); } });