/** * RAG 검색 챗봇 장치 제어 JavaScript (포트 스캔 기능 추가됨) */ // 장치 제어 모듈 const DeviceControl = { // 장치 제어 상태 isConnected: false, isStatusChecked: false, isLoadingPrograms: false, programsList: [], // DOM 요소들 elements: { // 탭 및 섹션 deviceTab: null, deviceSection: null, // 연결 관련 deviceServerUrlInput: null, connectDeviceServerBtn: null, deviceConnectionStatus: null, // 기본 기능 deviceBasicFunctions: null, checkDeviceStatusBtn: null, deviceStatusResult: null, // 프로그램 실행 (미리 정의된) deviceProgramControl: null, getProgramsBtn: null, programsList: null, programSelectDropdown: null, executeProgramBtn: null, executeResult: null, // 사용자 정의 프로그램 실행 deviceCustomControl: null, customCommandInput: null, executeCustomBtn: null, customExecuteResult: null, // ================== 포트 스캔 요소 추가 ================== devicePortScanControl: null, // 포트 스캔 섹션 컨테이너 scanPortsBtn: null, // 포트 스캔 버튼 portsStatus: null, // 포트 스캔 상태 표시 영역 portsResults: null // 포트 스캔 결과 표시 영역 // ================== 추가 끝 ============================ }, // 모듈 초기화 init: function() { console.log('장치 제어 모듈 초기화 중...'); this.initElements(); this.initEventListeners(); console.log('장치 제어 모듈 초기화 완료'); }, // DOM 요소 참조 초기화 initElements: function() { // 탭 및 섹션 this.elements.deviceTab = document.getElementById('deviceTab'); this.elements.deviceSection = document.getElementById('deviceSection'); // 연결 관련 this.elements.deviceServerUrlInput = document.getElementById('deviceServerUrlInput'); this.elements.connectDeviceServerBtn = document.getElementById('connectDeviceServerBtn'); this.elements.deviceConnectionStatus = document.getElementById('deviceConnectionStatus'); // 기본 기능 this.elements.deviceBasicFunctions = document.getElementById('deviceBasicFunctions'); this.elements.checkDeviceStatusBtn = document.getElementById('checkDeviceStatusBtn'); this.elements.deviceStatusResult = document.getElementById('deviceStatusResult'); // 프로그램 실행 (미리 정의된) this.elements.deviceProgramControl = document.getElementById('deviceProgramControl'); this.elements.getProgramsBtn = document.getElementById('getProgramsBtn'); this.elements.programsList = document.getElementById('programsList'); this.elements.programSelectDropdown = document.getElementById('programSelectDropdown'); this.elements.executeProgramBtn = document.getElementById('executeProgramBtn'); this.elements.executeResult = document.getElementById('executeResult'); // 사용자 정의 프로그램 실행 this.elements.deviceCustomControl = document.getElementById('deviceCustomControl'); this.elements.customCommandInput = document.getElementById('customCommandInput'); this.elements.executeCustomBtn = document.getElementById('executeCustomBtn'); this.elements.customExecuteResult = document.getElementById('customExecuteResult'); // ================== 포트 스캔 요소 초기화 추가 ================== this.elements.devicePortScanControl = document.getElementById('devicePortScanControl'); this.elements.scanPortsBtn = document.getElementById('scanPortsBtn'); this.elements.portsStatus = document.getElementById('portsStatus'); this.elements.portsResults = document.getElementById('portsResults'); // ================== 추가 끝 ================================= console.log('장치 제어 DOM 요소 참조 초기화 완료'); }, // 이벤트 리스너 등록 initEventListeners: function() { // 탭 전환 if (this.elements.deviceTab) { this.elements.deviceTab.addEventListener('click', () => { console.log('장치 제어 탭 클릭'); this.switchToDeviceTab(); }); } // 서버 연결 if (this.elements.connectDeviceServerBtn) { this.elements.connectDeviceServerBtn.addEventListener('click', () => { console.log('장치 서버 연결 버튼 클릭'); this.connectServer(); }); } // 엔터 키로 연결 if (this.elements.deviceServerUrlInput) { this.elements.deviceServerUrlInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { console.log('장치 서버 URL 입력 필드에서 엔터 키 감지'); event.preventDefault(); this.connectServer(); } }); } // 장치 상태 확인 if (this.elements.checkDeviceStatusBtn) { this.elements.checkDeviceStatusBtn.addEventListener('click', () => { console.log('장치 상태 확인 버튼 클릭'); this.checkDeviceStatus(); }); } // 프로그램 목록 조회 if (this.elements.getProgramsBtn) { this.elements.getProgramsBtn.addEventListener('click', () => { console.log('프로그램 목록 새로고침 버튼 클릭'); this.loadProgramsList(); }); } // 프로그램 선택 변경 if (this.elements.programSelectDropdown) { this.elements.programSelectDropdown.addEventListener('change', (event) => { console.log(`프로그램 선택 변경: ${event.target.value}`); this.updateExecuteButton(); }); } // 프로그램 실행 (미리 정의된) if (this.elements.executeProgramBtn) { this.elements.executeProgramBtn.addEventListener('click', () => { const programId = this.elements.programSelectDropdown.value; console.log(`미리 정의된 프로그램 실행 버튼 클릭, 선택된 ID: ${programId}`); this.executeProgram(programId); }); } // 사용자 정의 프로그램 실행 버튼 클릭 if (this.elements.executeCustomBtn) { this.elements.executeCustomBtn.addEventListener('click', () => { const command = this.elements.customCommandInput.value; console.log(`사용자 정의 프로그램 실행 버튼 클릭, 명령어: ${command}`); this.executeCustomProgram(command); }); } // 엔터 키로 사용자 정의 프로그램 실행 if (this.elements.customCommandInput) { this.elements.customCommandInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { console.log('사용자 정의 명령어 입력 필드에서 엔터 키 감지'); event.preventDefault(); // 폼 제출 방지 등 기본 동작 막기 const command = this.elements.customCommandInput.value; this.executeCustomProgram(command); } }); } // ================== 포트 스캔 이벤트 리스너 추가 ================== if (this.elements.scanPortsBtn) { this.elements.scanPortsBtn.addEventListener('click', () => { console.log('장치 포트 스캔 버튼 클릭'); this.scanDevicePorts(); }); } // ================== 추가 끝 ================================== console.log('장치 제어 이벤트 리스너 등록 완료'); }, // 장치 제어 탭으로 전환 switchToDeviceTab: function() { // 모든 탭과 탭 콘텐츠 비활성화 const tabs = document.querySelectorAll('.tab'); const tabContents = document.querySelectorAll('.tab-content'); tabs.forEach(tab => tab.classList.remove('active')); tabContents.forEach(content => content.classList.remove('active')); // 장치 제어 탭 활성화 if(this.elements.deviceTab) this.elements.deviceTab.classList.add('active'); if(this.elements.deviceSection) this.elements.deviceSection.classList.add('active'); console.log('장치 제어 탭으로 전환 완료'); }, // 서버 연결 함수 connectServer: async function() { // URL 가져오기 (입력된 것이 있으면 백업으로 사용) const inputUrl = this.elements.deviceServerUrlInput.value.trim(); // 연결 시도 중 UI 업데이트 if(this.elements.connectDeviceServerBtn) this.elements.connectDeviceServerBtn.disabled = true; this.updateConnectionStatus('connecting', '환경변수에 저장된 서버로 연결 시도 중...'); try { console.log('환경변수에 저장된 장치 서버로 연결 시도'); // 백엔드 API 호출하여 서버 상태 확인 const response = await AppUtils.fetchWithTimeout('/api/device/status', { method: 'GET' }, 10000); // 10초 타임아웃 const data = await response.json(); if (response.ok && data.success) { // 환경변수 URL 연결 성공 console.log('환경변수 설정 장치 서버 연결 성공:', data); this.isConnected = true; // 서버 응답에서 상태 메시지를 사용하거나 기본값 사용 const serverStatusMsg = data?.data?.status || data?.server_status || '정상'; this.updateConnectionStatus('connected', `서버 연결 성공! 상태: ${serverStatusMsg}`); // 기능 UI 활성화 if(this.elements.deviceBasicFunctions) this.elements.deviceBasicFunctions.classList.add('active'); if(this.elements.deviceProgramControl) this.elements.deviceProgramControl.classList.add('active'); if(this.elements.deviceCustomControl) this.elements.deviceCustomControl.classList.add('active'); // ================== 포트 스캔 UI 활성화 추가 ================== if(this.elements.devicePortScanControl) this.elements.devicePortScanControl.classList.add('active'); // ================== 추가 끝 ================================== // 장치 상태 자동 체크 this.checkDeviceStatus(); // 프로그램 목록 자동 로드 this.loadProgramsList(); // 시스템 알림 AppUtils.addSystemNotification(`장치 관리 서버 연결 성공! (환경변수 URL)`); } else { // 환경변수 URL 연결 실패, 입력된 URL로 시도 console.warn('환경변수 설정 장치 서버 연결 실패, 입력 URL로 재시도합니다:', data); if (!inputUrl) { console.error('입력된 URL이 없어 연결 실패'); this.isConnected = false; this.updateConnectionStatus('error', '환경변수 URL 연결 실패 및 입력된 URL이 없습니다. URL을 입력해주세요.'); return; // 입력 URL 없으면 여기서 종료 } // 입력 URL로 재시도 await this.connectWithCustomUrl(inputUrl); } } catch (error) { // 환경변수 URL 연결 시도 중 예외 발생 console.error('환경변수 URL 서버 연결 중 오류 발생:', error); this.isConnected = false; if (inputUrl) { console.warn('환경변수 URL 연결 시 오류 발생, 입력 URL로 재시도합니다'); await this.connectWithCustomUrl(inputUrl); // 입력 URL 재시도 } else { // 입력 URL도 없는 경우 최종 오류 처리 if (error.message.includes('시간이 초과')) { this.updateConnectionStatus('error', '환경변수 URL 연결 시간 초과. URL을 입력하여 다시 시도해주세요.'); } else { this.updateConnectionStatus('error', `환경변수 URL 연결 오류. URL을 입력하여 다시 시도해주세요: ${error.message}`); } } } finally { // 버튼 다시 활성화 if(this.elements.connectDeviceServerBtn) this.elements.connectDeviceServerBtn.disabled = false; } }, // 사용자 입력 URL로 연결 시도 (connectServer 내부 로직 분리) connectWithCustomUrl: async function(url) { this.updateConnectionStatus('connecting', `입력 URL(${url})로 연결 시도 중...`); console.log(`입력한 URL로 장치 서버 연결 시도: ${url}`); try { // 백엔드 API 호출 - Flask 백엔드가 URL 받아서 처리 const customUrlResponse = await AppUtils.fetchWithTimeout('/api/device/connect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: url }) }, 10000); const customUrlData = await customUrlResponse.json(); if (customUrlResponse.ok && customUrlData.success) { // 입력 URL 연결 성공 console.log('입력 URL 장치 서버 연결 성공:', customUrlData); this.isConnected = true; this.updateConnectionStatus('connected', `서버 연결 성공! 상태: ${customUrlData.server_status || '정상'}`); // 기능 UI 활성화 if(this.elements.deviceBasicFunctions) this.elements.deviceBasicFunctions.classList.add('active'); if(this.elements.deviceProgramControl) this.elements.deviceProgramControl.classList.add('active'); if(this.elements.deviceCustomControl) this.elements.deviceCustomControl.classList.add('active'); // ================== 포트 스캔 UI 활성화 추가 ================== if(this.elements.devicePortScanControl) this.elements.devicePortScanControl.classList.add('active'); // ================== 추가 끝 ================================== // 장치 상태 자동 체크 this.checkDeviceStatus(); // 프로그램 목록 자동 로드 this.loadProgramsList(); // 시스템 알림 AppUtils.addSystemNotification(`장치 관리 서버 연결 성공! (${url})`); } else { // 입력 URL 연결 실패 console.error('입력 URL 장치 서버 연결 실패:', customUrlData); this.isConnected = false; this.updateConnectionStatus('error', `서버 연결 실패: ${customUrlData.error || '서버 응답 오류'}`); } } catch(error) { // 입력 URL로 시도 중 오류 console.error('입력 URL로 재시도 중 오류 발생:', error); this.isConnected = false; if (error.message.includes('시간이 초과')) { this.updateConnectionStatus('error', '서버 연결 시간 초과. 서버가 실행 중인지 확인해주세요.'); } else { this.updateConnectionStatus('error', `서버 연결 오류: ${error.message}`); } } }, // 연결 상태 업데이트 updateConnectionStatus: function(status, message) { const statusElement = this.elements.deviceConnectionStatus; if (!statusElement) return; // 요소 없으면 종료 // 모든 상태 클래스 제거 statusElement.classList.remove('connected', 'disconnected', 'error', 'connecting'); // 상태에 따라 클래스 추가 statusElement.classList.add(status); // 메시지 업데이트 statusElement.textContent = message; console.log(`연결 상태 업데이트: ${status} - ${message}`); }, // 장치 상태 확인 checkDeviceStatus: async function() { if (!this.isConnected) { if(this.elements.deviceStatusResult) this.elements.deviceStatusResult.value = '오류: 먼저 서버에 연결해야 합니다.'; console.error('장치 상태 확인 시도 중 오류: 서버 연결 안됨'); return; } // 상태 확인 중 UI 업데이트 if(this.elements.checkDeviceStatusBtn) this.elements.checkDeviceStatusBtn.disabled = true; if(this.elements.deviceStatusResult) this.elements.deviceStatusResult.value = '장치 상태 확인 중...'; try { console.log('장치 상태 확인 요청 전송'); // 백엔드 API 호출 const response = await AppUtils.fetchWithTimeout('/api/device/status', { method: 'GET' }); const data = await response.json(); if (response.ok && data.success) { console.log('장치 상태 확인 성공:', data); this.isStatusChecked = true; // JSON 데이터를 보기 좋게 포맷팅하여 표시 if(this.elements.deviceStatusResult) this.elements.deviceStatusResult.value = JSON.stringify(data.data || data, null, 2); // data.data 우선 확인 } else { console.error('장치 상태 확인 실패:', data); if(this.elements.deviceStatusResult) this.elements.deviceStatusResult.value = `상태 확인 실패: ${data.error || '알 수 없는 오류'}`; } } catch (error) { console.error('장치 상태 확인 중 오류 발생:', error); if(this.elements.deviceStatusResult) this.elements.deviceStatusResult.value = `상태 확인 중 오류 발생: ${error.message}`; } finally { if(this.elements.checkDeviceStatusBtn) this.elements.checkDeviceStatusBtn.disabled = false; } }, // 프로그램 목록 조회 loadProgramsList: async function() { if (!this.isConnected) { this.showProgramsError('오류: 먼저 서버에 연결해야 합니다.'); console.error('프로그램 목록 조회 시도 중 오류: 서버 연결 안됨'); return; } if (this.isLoadingPrograms) { console.log('이미 프로그램 목록 로딩 중'); return; } // 로딩 중 UI 업데이트 this.isLoadingPrograms = true; if(this.elements.getProgramsBtn) this.elements.getProgramsBtn.disabled = true; if(this.elements.programsList) { this.elements.programsList.innerHTML = `
${AppUtils.createLoadingSpinner()} 프로그램 목록 로드 중...
`; } try { console.log('프로그램 목록 조회 요청 전송'); // 백엔드 API 호출 const response = await AppUtils.fetchWithTimeout('/api/device/programs', { method: 'GET' }); const data = await response.json(); if (response.ok && data.success) { console.log('프로그램 목록 조회 성공:', data); this.programsList = data.programs || []; this.displayProgramsList(); this.updateProgramsDropdown(); this.updateExecuteButton(); } else { console.error('프로그램 목록 조회 실패:', data); this.showProgramsError(`프로그램 목록 조회 실패: ${data.error || '알 수 없는 오류'}`); } } catch (error) { console.error('프로그램 목록 조회 중 오류 발생:', error); this.showProgramsError(`프로그램 목록 조회 중 오류 발생: ${error.message}`); } finally { this.isLoadingPrograms = false; if(this.elements.getProgramsBtn) this.elements.getProgramsBtn.disabled = false; } }, // 프로그램 목록 표시 displayProgramsList: function() { const programsListElement = this.elements.programsList; if (!programsListElement) return; if (!this.programsList || this.programsList.length === 0) { programsListElement.innerHTML = `
등록된 프로그램이 없습니다.
`; return; } // 테이블 형태로 프로그램 목록 표시 let html = ``; this.programsList.forEach(program => { html += ``; }); html += `
이름설명경로
${AppUtils.escapeHtml(program.name || '알 수 없음')} ${AppUtils.escapeHtml(program.description || '-')} ${AppUtils.escapeHtml(program.path || '-')}
총 ${this.programsList.length}개 프로그램
`; programsListElement.innerHTML = html; }, // 프로그램 드롭다운 업데이트 updateProgramsDropdown: function() { const dropdown = this.elements.programSelectDropdown; if (!dropdown) return; dropdown.innerHTML = ''; // 기존 옵션 제거 const defaultOption = document.createElement('option'); defaultOption.value = ''; defaultOption.textContent = this.programsList.length > 0 ? '-- 실행할 프로그램 선택 --' : '-- 프로그램 없음 --'; dropdown.appendChild(defaultOption); this.programsList.forEach(program => { const option = document.createElement('option'); option.value = program.id || ''; option.textContent = program.name || '알 수 없음'; if (program.description) { option.textContent += ` (${program.description})`; } dropdown.appendChild(option); }); }, // 실행 버튼 상태 업데이트 updateExecuteButton: function() { const dropdown = this.elements.programSelectDropdown; const executeBtn = this.elements.executeProgramBtn; if (!dropdown || !executeBtn) return; executeBtn.disabled = !dropdown.value; // 선택된 프로그램이 있을 때만 활성화 }, // 프로그램 실행 (미리 정의된) executeProgram: async function(programId) { if (!this.isConnected) { this.showExecuteResult('error', '오류: 먼저 서버에 연결해야 합니다.'); return; } if (!programId) { this.showExecuteResult('error', '오류: 실행할 프로그램을 선택해주세요.'); return; } // 실행 중 UI 업데이트 if(this.elements.executeProgramBtn) this.elements.executeProgramBtn.disabled = true; this.showExecuteResult('loading', '프로그램 실행 중...'); try { console.log(`프로그램 실행 요청 전송: ${programId}`); // 백엔드 API 호출 const response = await AppUtils.fetchWithTimeout(`/api/device/programs/${programId}/execute`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }, 15000); // 15초 타임아웃 const data = await response.json(); if (response.ok && data.success) { console.log('프로그램 실행 성공:', data); this.showExecuteResult('success', `실행 성공: ${data.message || '프로그램이 성공적으로 실행되었습니다.'}`); AppUtils.addSystemNotification(`프로그램 실행 성공: ${this.getSelectedProgramName()}`); } else { console.error('프로그램 실행 실패:', data); this.showExecuteResult('error', `실행 실패: ${data.error || '알 수 없는 오류'}`); } } catch (error) { console.error('프로그램 실행 중 오류 발생:', error); if (error.message.includes('시간이 초과')) { this.showExecuteResult('error', '프로그램 실행 요청 시간 초과. 서버 응답이 없습니다.'); } else { this.showExecuteResult('error', `프로그램 실행 중 오류 발생: ${error.message}`); } } finally { if(this.elements.executeProgramBtn) this.elements.executeProgramBtn.disabled = false; } }, // 선택된 프로그램 이름 가져오기 getSelectedProgramName: function() { const dropdown = this.elements.programSelectDropdown; if (!dropdown || dropdown.selectedIndex < 0) return '알 수 없는 프로그램'; // 인덱스 체크 추가 const selectedOption = dropdown.options[dropdown.selectedIndex]; return selectedOption ? selectedOption.textContent : '알 수 없는 프로그램'; }, // 프로그램 목록 오류 표시 showProgramsError: function(errorMessage) { const programsListElement = this.elements.programsList; if (!programsListElement) return; programsListElement.innerHTML = `
${errorMessage}
`; const retryBtn = document.getElementById('retryLoadProgramsBtn'); if (retryBtn) { // 기존 리스너 제거 후 추가 (중복 방지) retryBtn.replaceWith(retryBtn.cloneNode(true)); document.getElementById('retryLoadProgramsBtn').addEventListener('click', () => { console.log('프로그램 목록 재시도 버튼 클릭'); this.loadProgramsList(); }); } }, // 실행 결과 표시 (미리 정의된 프로그램 용) showExecuteResult: function(status, message) { const resultElement = this.elements.executeResult; if (!resultElement) return; resultElement.classList.remove('success', 'error', 'warning'); // 모든 상태 클래스 제거 resultElement.innerHTML = ''; // 내용 초기화 switch (status) { case 'success': resultElement.classList.add('success'); resultElement.innerHTML = ` ${message}`; break; case 'error': resultElement.classList.add('error'); resultElement.innerHTML = ` ${message}`; break; case 'warning': resultElement.classList.add('warning'); resultElement.innerHTML = ` ${message}`; break; case 'loading': resultElement.innerHTML = `${AppUtils.createLoadingSpinner()} ${message}`; break; default: resultElement.textContent = message; } }, // 사용자 정의 프로그램 실행 executeCustomProgram: async function(command) { if (!this.isConnected) { this.showCustomExecuteResult('error', '오류: 먼저 서버에 연결해야 합니다.'); return; } if (!command || command.trim() === '') { this.showCustomExecuteResult('error', '오류: 실행할 명령어를 입력해주세요.'); return; } // 실행 중 UI 업데이트 if(this.elements.executeCustomBtn) this.elements.executeCustomBtn.disabled = true; this.showCustomExecuteResult('loading', '명령어 실행 중...'); try { console.log(`사용자 정의 프로그램 실행 요청 전송: ${command}`); // 백엔드 API 호출 const response = await AppUtils.fetchWithTimeout('/api/device/execute-custom', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ command: command }) }, 15000); // 15초 타임아웃 const data = await response.json(); if (response.ok && data.success) { console.log('사용자 정의 프로그램 실행 성공:', data); let successMessage = `명령어 실행 성공: ${data.message || ''}`; if (data.output && data.output.trim()) { successMessage += `
${AppUtils.escapeHtml(data.output)}
`; } this.showCustomExecuteResult('success', successMessage); AppUtils.addSystemNotification(`사용자 정의 명령어 실행 성공: ${command}`); } else { console.error('사용자 정의 프로그램 실행 실패:', data); let errorMessage = `실행 실패: ${data.error || '알 수 없는 오류'}`; if (data.error_output && data.error_output.trim()) { errorMessage += `
${AppUtils.escapeHtml(data.error_output)}
`; } this.showCustomExecuteResult('error', errorMessage); } } catch (error) { console.error('사용자 정의 프로그램 실행 중 오류 발생:', error); if (error.message.includes('시간이 초과')) { this.showCustomExecuteResult('error', '명령어 실행 요청 시간 초과. 서버 응답이 없습니다.'); } else { this.showCustomExecuteResult('error', `명령어 실행 중 오류 발생: ${error.message}`); } } finally { if(this.elements.executeCustomBtn) this.elements.executeCustomBtn.disabled = false; } }, // 사용자 정의 프로그램 실행 결과 표시 showCustomExecuteResult: function(status, message) { const resultElement = this.elements.customExecuteResult; if (!resultElement) return; resultElement.classList.remove('success', 'error', 'warning'); // 모든 상태 클래스 제거 resultElement.innerHTML = ''; // 내용 초기화 switch (status) { case 'success': resultElement.classList.add('success'); resultElement.innerHTML = ` ${message}`; break; case 'error': resultElement.classList.add('error'); resultElement.innerHTML = ` ${message}`; break; case 'warning': resultElement.classList.add('warning'); resultElement.innerHTML = ` ${message}`; break; case 'loading': resultElement.innerHTML = `${AppUtils.createLoadingSpinner()} ${message}`; break; default: resultElement.textContent = message; // 기본은 텍스트만 표시 } }, // ================== 포트 스캔 함수 추가 ================== // 장치 포트 스캔 함수 scanDevicePorts: async function() { if (!this.isConnected) { this.showPortsStatus('error', '오류: 먼저 서버에 연결해야 합니다.'); console.error('장치 포트 스캔 시도 중 오류: 서버 연결 안됨'); return; } // 스캔 중 UI 업데이트 if(this.elements.scanPortsBtn) this.elements.scanPortsBtn.disabled = true; this.showPortsStatus('loading', '장치 포트 스캔 중...'); try { console.log('장치 포트 스캔 요청 전송'); // 백엔드 API 호출 const response = await AppUtils.fetchWithTimeout('/api/device/scan-ports', { method: 'GET' }, 30000); // 장치 스캔은 시간이 오래 걸릴 수 있으므로 타임아웃 30초로 설정 const data = await response.json(); if (response.ok && data.success) { // 스캔 성공 console.log('장치 포트 스캔 성공:', data); // 결과 표시 this.displayPortsResults(data); this.showPortsStatus('success', '장치 포트 스캔이 완료되었습니다.'); } else { // 스캔 실패 console.error('장치 포트 스캔 실패:', data); this.showPortsStatus('error', `스캔 실패: ${data.error || '알 수 없는 오류'}`); } } catch (error) { // 예외 발생 console.error('장치 포트 스캔 중 오류 발생:', error); if (error.message.includes('시간이 초과')) { this.showPortsStatus('error', '장치 포트 스캔 요청 시간 초과. 서버 응답이 없습니다.'); } else { this.showPortsStatus('error', `장치 포트 스캔 중 오류 발생: ${error.message}`); } } finally { // 버튼 다시 활성화 if(this.elements.scanPortsBtn) this.elements.scanPortsBtn.disabled = false; } }, // 장치 포트 스캔 결과 표시 displayPortsResults: function(data) { const portsResultsElement = this.elements.portsResults; if (!portsResultsElement) return; // 요소 없으면 종료 // 시스템 정보 const systemInfo = data.system_info || {}; const devices = data.devices || {}; const serialPorts = devices.serial || []; const usbDevices = devices.usb || []; const windowsUsbDevices = devices.windows_usb || []; // windows_usb 키 추가 // HTML 생성 시작 let html = `
`; // 시스템 정보 섹션 html += `

시스템 정보

OS:${AppUtils.escapeHtml(systemInfo.os || '알 수 없음')} ${AppUtils.escapeHtml(systemInfo.version || '')}
시스템:${AppUtils.escapeHtml(systemInfo.platform || '알 수 없음')}
조회 시간:${AppUtils.escapeHtml(data.timestamp || '알 수 없음')}
`; // 시리얼 포트 섹션 html += `

시리얼 포트 (${serialPorts.length}개)

`; if (serialPorts.length === 0) { html += `

발견된 시리얼 포트가 없습니다.

`; } else { html += ``; serialPorts.forEach(port => { const vidPid = (port.vid && port.pid) ? `${port.vid}:${port.pid}` : '알 수 없음'; html += ``; }); html += `
포트설명제조사VID:PID
${AppUtils.escapeHtml(port.port || '')} ${AppUtils.escapeHtml(port.description || '')} ${AppUtils.escapeHtml(port.manufacturer || '')} ${AppUtils.escapeHtml(vidPid)}
`; } html += `
`; // USB 장치 섹션 (pyserial list_ports 기반) html += `

USB 장치 (pyserial) (${usbDevices.length}개)

`; if (usbDevices.length === 0) { html += `

발견된 USB 장치가 없습니다.

`; } else { html += ``; usbDevices.forEach(device => { const vidPid = (device.vid && device.pid) ? `${device.vid}:${device.pid}` : '알 수 없음'; html += ``; }); html += `
VID:PID제품명제조사위치
${AppUtils.escapeHtml(vidPid)} ${AppUtils.escapeHtml(device.product || '')} ${AppUtils.escapeHtml(device.manufacturer || '')} ${AppUtils.escapeHtml(device.location || '')}
`; } html += `
`; // Windows WMI USB 장치 섹션 (있는 경우) if (windowsUsbDevices && windowsUsbDevices.length > 0) { html += `

Windows USB 장치 (WMI) (${windowsUsbDevices.length}개)

`; html += ``; windowsUsbDevices.forEach(device => { const vidPid = (device.vid && device.pid) ? `${device.vid}:${device.pid}` : '알 수 없음'; html += ``; }); html += `
이름설명제조사VID:PID
${AppUtils.escapeHtml(device.name || '')} ${AppUtils.escapeHtml(device.description || '')} ${AppUtils.escapeHtml(device.manufacturer || '')} ${AppUtils.escapeHtml(vidPid)}
`; } // 컨테이너 닫기 html += `
`; // HTML 설정 portsResultsElement.innerHTML = html; }, // 포트 스캔 상태 표시 showPortsStatus: function(status, message) { const statusElement = this.elements.portsStatus; if (!statusElement) return; // 요소 없으면 종료 // 모든 상태 클래스 제거 statusElement.classList.remove('success', 'error', 'warning'); // 내용 초기화 statusElement.innerHTML = ''; // 상태에 따라 처리 switch (status) { case 'success': statusElement.classList.add('success'); statusElement.innerHTML = ` ${message}`; break; case 'error': statusElement.classList.add('error'); statusElement.innerHTML = ` ${message}`; break; case 'warning': statusElement.classList.add('warning'); statusElement.innerHTML = ` ${message}`; break; case 'loading': statusElement.innerHTML = `${AppUtils.createLoadingSpinner()} ${message}`; break; default: statusElement.textContent = message; } } // ================== 추가 끝 ============================ }; // DeviceControl 객체 끝 // 페이지 로드 완료 시 모듈 초기화 document.addEventListener('DOMContentLoaded', function() { console.log('장치 제어 모듈 로드됨'); // DOM이 완전히 로드된 후 약간의 지연을 두고 초기화 // DOM 요소가 확실히 로드된 후에 초기화하기 위함 setTimeout(() => { // AppUtils가 정의되었는지 확인 (의존성) if (typeof AppUtils === 'undefined') { console.error('AppUtils가 정의되지 않아 DeviceControl 초기화 실패.'); // 필요하다면 사용자에게 알림 표시 alert('페이지 초기화 오류: 필수 유틸리티(AppUtils)를 로드할 수 없습니다.'); return; } DeviceControl.init(); }, 100); });