/**
* 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 += `
`;
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 += `
포트
설명
제조사
VID:PID
`;
serialPorts.forEach(port => {
const vidPid = (port.vid && port.pid) ? `${port.vid}:${port.pid}` : '알 수 없음';
html += `
${AppUtils.escapeHtml(port.port || '')}
${AppUtils.escapeHtml(port.description || '')}
${AppUtils.escapeHtml(port.manufacturer || '')}
${AppUtils.escapeHtml(vidPid)}
`;
});
html += `
`;
}
html += `
`;
// USB 장치 섹션 (pyserial list_ports 기반)
html += `
USB 장치 (pyserial) (${usbDevices.length}개)
`;
if (usbDevices.length === 0) {
html += `
발견된 USB 장치가 없습니다.
`;
} else {
html += `
VID:PID
제품명
제조사
위치
`;
usbDevices.forEach(device => {
const vidPid = (device.vid && device.pid) ? `${device.vid}:${device.pid}` : '알 수 없음';
html += `
${AppUtils.escapeHtml(vidPid)}
${AppUtils.escapeHtml(device.product || '')}
${AppUtils.escapeHtml(device.manufacturer || '')}
${AppUtils.escapeHtml(device.location || '')}
`;
});
html += `
`;
}
html += `
`;
// Windows WMI USB 장치 섹션 (있는 경우)
if (windowsUsbDevices && windowsUsbDevices.length > 0) {
html += `
Windows USB 장치 (WMI) (${windowsUsbDevices.length}개)
`;
html += `
이름
설명
제조사
VID:PID
`;
windowsUsbDevices.forEach(device => {
const vidPid = (device.vid && device.pid) ? `${device.vid}:${device.pid}` : '알 수 없음';
html += `
${AppUtils.escapeHtml(device.name || '')}
${AppUtils.escapeHtml(device.description || '')}
${AppUtils.escapeHtml(device.manufacturer || '')}
${AppUtils.escapeHtml(vidPid)}
`;
});
html += `
`;
}
// 컨테이너 닫기
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);
});