jeongsoo's picture
init
6575706
/**
* 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,
// ================== μΆ”κ°€ μ‹œμž‘ 1/4 ==================
// μ‚¬μš©μž μ •μ˜ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰
deviceCustomControl: null,
customCommandInput: null,
executeCustomBtn: null,
customExecuteResult: null
// ================== μΆ”κ°€ 끝 1/4 ====================
},
// λͺ¨λ“ˆ μ΄ˆκΈ°ν™”
init: function() {
console.log('μž₯치 μ œμ–΄ λͺ¨λ“ˆ μ΄ˆκΈ°ν™” 쀑...');
// DOM μš”μ†Œ μ°Έμ‘° κ°€μ Έμ˜€κΈ°
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');
// ================== μΆ”κ°€ μ‹œμž‘ 2/4 ==================
// μ‚¬μš©μž μ •μ˜ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰
this.elements.deviceCustomControl = document.getElementById('deviceCustomControl');
this.elements.customCommandInput = document.getElementById('customCommandInput');
this.elements.executeCustomBtn = document.getElementById('executeCustomBtn');
this.elements.customExecuteResult = document.getElementById('customExecuteResult');
// ================== μΆ”κ°€ 끝 2/4 ====================
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);
});
}
// ================== μΆ”κ°€ μ‹œμž‘ 3/4 ==================
// μ‚¬μš©μž μ •μ˜ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ λ²„νŠΌ 클릭
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);
}
});
}
// ================== μΆ”κ°€ 끝 3/4 ====================
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'));
// μž₯치 μ œμ–΄ νƒ­ ν™œμ„±ν™”
this.elements.deviceTab.classList.add('active');
this.elements.deviceSection.classList.add('active');
console.log('μž₯치 μ œμ–΄ νƒ­μœΌλ‘œ μ „ν™˜ μ™„λ£Œ');
},
// μ„œλ²„ μ—°κ²° ν•¨μˆ˜
connectServer: async function() {
// URL κ°€μ Έμ˜€κΈ° (μž…λ ₯된 것이 있으면 λ°±μ—…μœΌλ‘œ μ‚¬μš©)
const inputUrl = this.elements.deviceServerUrlInput.value.trim();
// μ—°κ²° μ‹œλ„ 쀑 UI μ—…λ°μ΄νŠΈ
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) {
// μ—°κ²° 성곡
console.log('ν™˜κ²½λ³€μˆ˜ μ„€μ • μž₯치 μ„œλ²„ μ—°κ²° 성곡:', data);
this.isConnected = true;
this.updateConnectionStatus('connected', `μ„œλ²„ μ—°κ²° 성곡! μƒνƒœ: ${data.server_status || '정상'}`);
// κΈ°λŠ₯ UI ν™œμ„±ν™”
if(this.elements.deviceBasicFunctions) this.elements.deviceBasicFunctions.classList.add('active');
if(this.elements.deviceProgramControl) this.elements.deviceProgramControl.classList.add('active');
// ================== μΆ”κ°€ (connectServer 성곡 μ‹œ UI ν™œμ„±ν™”) ==================
if(this.elements.deviceCustomControl) this.elements.deviceCustomControl.classList.add('active');
// ================== μΆ”κ°€ 끝 =============================================
// μž₯치 μƒνƒœ μžλ™ 체크
this.checkDeviceStatus();
// ν”„λ‘œκ·Έλž¨ λͺ©λ‘ μžλ™ λ‘œλ“œ
this.loadProgramsList();
// μ‹œμŠ€ν…œ μ•Œλ¦Ό
AppUtils.addSystemNotification(`μž₯치 관리 μ„œλ²„ μ—°κ²° 성곡! (ν™˜κ²½λ³€μˆ˜ URL)`);
} else {
// ν™˜κ²½λ³€μˆ˜ URL μ—°κ²° μ‹€νŒ¨, μž…λ ₯된 URL둜 μ‹œλ„
console.warn('ν™˜κ²½λ³€μˆ˜ μ„€μ • μž₯치 μ„œλ²„ μ—°κ²° μ‹€νŒ¨, μž…λ ₯ URL둜 μž¬μ‹œλ„ν•©λ‹ˆλ‹€:', data);
// μž…λ ₯ URL이 μžˆλŠ”μ§€ 확인
if (!inputUrl) {
console.error('μž…λ ₯된 URL이 μ—†μ–΄ μ—°κ²° μ‹€νŒ¨');
this.isConnected = false;
this.updateConnectionStatus('error', 'ν™˜κ²½λ³€μˆ˜ URL μ—°κ²° μ‹€νŒ¨ 및 μž…λ ₯된 URL이 μ—†μŠ΅λ‹ˆλ‹€. URL을 μž…λ ₯ν•΄μ£Όμ„Έμš”.');
return;
}
// μž…λ ₯ URL둜 μž¬μ‹œλ„
this.updateConnectionStatus('connecting', `μž…λ ₯ URL(${inputUrl})둜 μ—°κ²° μ‹œλ„ 쀑...`);
console.log(`μž…λ ₯ν•œ URL둜 μž₯치 μ„œλ²„ μ—°κ²° μ‹œλ„: ${inputUrl}`);
// λ°±μ—”λ“œ API 호좜 - μž…λ ₯ URL μ‚¬μš©
const customUrlResponse = await AppUtils.fetchWithTimeout('/api/device/connect', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ url: inputUrl })
}, 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');
// ================== μΆ”κ°€ (connectServer 성곡 μ‹œ UI ν™œμ„±ν™”) ==================
if(this.elements.deviceCustomControl) this.elements.deviceCustomControl.classList.add('active');
// ================== μΆ”κ°€ 끝 =============================================
// μž₯치 μƒνƒœ μžλ™ 체크
this.checkDeviceStatus();
// ν”„λ‘œκ·Έλž¨ λͺ©λ‘ μžλ™ λ‘œλ“œ
this.loadProgramsList();
// μ‹œμŠ€ν…œ μ•Œλ¦Ό
AppUtils.addSystemNotification(`μž₯치 관리 μ„œλ²„ μ—°κ²° 성곡! (${inputUrl})`);
} else {
// μž…λ ₯ URL μ—°κ²° μ‹€νŒ¨
console.error('μž…λ ₯ URL μž₯치 μ„œλ²„ μ—°κ²° μ‹€νŒ¨:', customUrlData);
this.isConnected = false;
this.updateConnectionStatus('error', `μ„œλ²„ μ—°κ²° μ‹€νŒ¨: ${customUrlData.error || 'μ„œλ²„ 응닡 였λ₯˜'}`);
}
}
} catch (error) {
// μ˜ˆμ™Έ λ°œμƒ
console.error('μ„œλ²„ μ—°κ²° 쀑 였λ₯˜ λ°œμƒ:', error);
this.isConnected = false;
// ν™˜κ²½λ³€μˆ˜ URL μ—°κ²° μ‹€νŒ¨, μž…λ ₯된 URL둜 μ‹œλ„
if (inputUrl) {
console.warn('ν™˜κ²½λ³€μˆ˜ URL μ—°κ²° μ‹œ 였λ₯˜ λ°œμƒ, μž…λ ₯ URL둜 μž¬μ‹œλ„ν•©λ‹ˆλ‹€');
try {
// μž…λ ₯ URL둜 μž¬μ‹œλ„
this.updateConnectionStatus('connecting', `μž…λ ₯ URL(${inputUrl})둜 μ—°κ²° μ‹œλ„ 쀑...`);
console.log(`μž…λ ₯ν•œ URL둜 μž₯치 μ„œλ²„ μ—°κ²° μ‹œλ„: ${inputUrl}`);
// λ°±μ—”λ“œ API 호좜 - μž…λ ₯ URL μ‚¬μš©
const customUrlResponse = await AppUtils.fetchWithTimeout('/api/device/connect', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ url: inputUrl })
}, 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');
// ================== μΆ”κ°€ (connectServer 성곡 μ‹œ UI ν™œμ„±ν™”) ==================
if(this.elements.deviceCustomControl) this.elements.deviceCustomControl.classList.add('active');
// ================== μΆ”κ°€ 끝 =============================================
// μž₯치 μƒνƒœ μžλ™ 체크
this.checkDeviceStatus();
// ν”„λ‘œκ·Έλž¨ λͺ©λ‘ μžλ™ λ‘œλ“œ
this.loadProgramsList();
// μ‹œμŠ€ν…œ μ•Œλ¦Ό
AppUtils.addSystemNotification(`μž₯치 관리 μ„œλ²„ μ—°κ²° 성곡! (${inputUrl})`);
return; // μ„±κ³΅ν•˜λ©΄ μ—¬κΈ°μ„œ μ’…λ£Œ
} else {
// μž…λ ₯ URL μ—°κ²° μ‹€νŒ¨
console.error('μž…λ ₯ URL μž₯치 μ„œλ²„ μ—°κ²° μ‹€νŒ¨:', customUrlData);
this.updateConnectionStatus('error', `μ„œλ²„ μ—°κ²° μ‹€νŒ¨: ${customUrlData.error || 'μ„œλ²„ 응닡 였λ₯˜'}`);
}
} catch (inputUrlError) {
// μž…λ ₯ URL둜 μž¬μ‹œλ„ 쀑 였λ₯˜
console.error('μž…λ ₯ URL둜 μž¬μ‹œλ„ 쀑 였λ₯˜ λ°œμƒ:', inputUrlError);
if (inputUrlError.message.includes('μ‹œκ°„μ΄ 초과')) {
this.updateConnectionStatus('error', 'μ„œλ²„ μ—°κ²° μ‹œκ°„ 초과. μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지 ν™•μΈν•΄μ£Όμ„Έμš”.');
} else {
this.updateConnectionStatus('error', `μ„œλ²„ μ—°κ²° 였λ₯˜: ${inputUrlError.message}`);
}
}
} else {
// ν…μŠ€νŠΈλ°•μŠ€μ— URL이 μ—†λŠ” 경우
if (error.message.includes('μ‹œκ°„μ΄ 초과')) {
this.updateConnectionStatus('error', 'ν™˜κ²½λ³€μˆ˜ URL μ—°κ²° μ‹œκ°„ 초과. URL을 μž…λ ₯ν•˜μ—¬ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.');
} else {
this.updateConnectionStatus('error', `ν™˜κ²½λ³€μˆ˜ URL μ—°κ²° 였λ₯˜. URL을 μž…λ ₯ν•˜μ—¬ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”: ${error.message}`);
}
}
} finally {
// λ²„νŠΌ λ‹€μ‹œ ν™œμ„±ν™”
this.elements.connectDeviceServerBtn.disabled = false;
}
},
// μ—°κ²° μƒνƒœ μ—…λ°μ΄νŠΈ
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) {
this.elements.deviceStatusResult.value = '였λ₯˜: λ¨Όμ € μ„œλ²„μ— μ—°κ²°ν•΄μ•Ό ν•©λ‹ˆλ‹€.';
console.error('μž₯치 μƒνƒœ 확인 μ‹œλ„ 쀑 였λ₯˜: μ„œλ²„ μ—°κ²° μ•ˆλ¨');
return;
}
// μƒνƒœ 확인 쀑 UI μ—…λ°μ΄νŠΈ
this.elements.checkDeviceStatusBtn.disabled = true;
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 데이터λ₯Ό 보기 μ’‹κ²Œ ν¬λ§·νŒ…ν•˜μ—¬ ν‘œμ‹œ
this.elements.deviceStatusResult.value = JSON.stringify(data.data || data, null, 2); // data.data μš°μ„  확인
} else {
// μƒνƒœ 확인 μ‹€νŒ¨
console.error('μž₯치 μƒνƒœ 확인 μ‹€νŒ¨:', data);
this.elements.deviceStatusResult.value = `μƒνƒœ 확인 μ‹€νŒ¨: ${data.error || 'μ•Œ 수 μ—†λŠ” 였λ₯˜'}`;
}
} catch (error) {
// μ˜ˆμ™Έ λ°œμƒ
console.error('μž₯치 μƒνƒœ 확인 쀑 였λ₯˜ λ°œμƒ:', error);
this.elements.deviceStatusResult.value = `μƒνƒœ 확인 쀑 였λ₯˜ λ°œμƒ: ${error.message}`;
} finally {
// λ²„νŠΌ λ‹€μ‹œ ν™œμ„±ν™”
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 = `
<div class="loading-message">
${AppUtils.createLoadingSpinner()} ν”„λ‘œκ·Έλž¨ λͺ©λ‘ λ‘œλ“œ 쀑...
</div>
`;
}
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 = `
<div class="no-programs-message">
<i class="fas fa-info-circle"></i> λ“±λ‘λœ ν”„λ‘œκ·Έλž¨μ΄ μ—†μŠ΅λ‹ˆλ‹€.
</div>
`;
return;
}
// ν…Œμ΄λΈ” ν˜•νƒœλ‘œ ν”„λ‘œκ·Έλž¨ λͺ©λ‘ ν‘œμ‹œ
let html = `
<table class="program-list">
<thead>
<tr>
<th>이름</th>
<th>μ„€λͺ…</th>
<th>경둜</th>
</tr>
</thead>
<tbody>
`;
// ν”„λ‘œκ·Έλž¨ ν•­λͺ© 생성
this.programsList.forEach(program => {
html += `
<tr>
<td>${AppUtils.escapeHtml(program.name || 'μ•Œ 수 μ—†μŒ')}</td>
<td>${AppUtils.escapeHtml(program.description || '-')}</td>
<td>${AppUtils.escapeHtml(program.path || '-')}</td>
</tr>
`;
});
html += `
</tbody>
</table>
<div style="margin-top: 10px; font-size: 0.9em; color: #666;">
총 ${this.programsList.length}개 ν”„λ‘œκ·Έλž¨
</div>
`;
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', '였λ₯˜: λ¨Όμ € μ„œλ²„μ— μ—°κ²°ν•΄μ•Ό ν•©λ‹ˆλ‹€.');
console.error('ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μ‹œλ„ 쀑 였λ₯˜: μ„œλ²„ μ—°κ²° μ•ˆλ¨');
return;
}
if (!programId) {
this.showExecuteResult('error', '였λ₯˜: μ‹€ν–‰ν•  ν”„λ‘œκ·Έλž¨μ„ μ„ νƒν•΄μ£Όμ„Έμš”.');
console.error('ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μ‹œλ„ 쀑 였λ₯˜: ν”„λ‘œκ·Έλž¨ ID μ—†μŒ');
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) return 'μ•Œ 수 μ—†λŠ” ν”„λ‘œκ·Έλž¨';
const selectedOption = dropdown.options[dropdown.selectedIndex];
return selectedOption ? selectedOption.textContent : 'μ•Œ 수 μ—†λŠ” ν”„λ‘œκ·Έλž¨';
},
// ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 였λ₯˜ ν‘œμ‹œ
showProgramsError: function(errorMessage) {
const programsListElement = this.elements.programsList;
if (!programsListElement) return; // μš”μ†Œ μ—†μœΌλ©΄ μ’…λ£Œ
programsListElement.innerHTML = `
<div class="error-message">
<i class="fas fa-exclamation-circle"></i> ${errorMessage}
<button class="retry-button" id="retryLoadProgramsBtn">
<i class="fas fa-sync"></i> λ‹€μ‹œ μ‹œλ„
</button>
</div>
`;
// μž¬μ‹œλ„ λ²„νŠΌ 이벀트 λ¦¬μŠ€λ„ˆ
// 이전에 μΆ”κ°€λœ λ¦¬μŠ€λ„ˆκ°€ μžˆλ‹€λ©΄ μ œκ±°ν•˜κ³  λ‹€μ‹œ μΆ”κ°€ν•˜λŠ” 것이 μ•ˆμ „ν•  수 있음
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 = `<i class="fas fa-check-circle"></i> ${message}`;
break;
case 'error':
resultElement.classList.add('error');
resultElement.innerHTML = `<i class="fas fa-exclamation-circle"></i> ${message}`;
break;
case 'warning':
resultElement.classList.add('warning');
resultElement.innerHTML = `<i class="fas fa-exclamation-triangle"></i> ${message}`;
break;
case 'loading':
resultElement.innerHTML = `${AppUtils.createLoadingSpinner()} ${message}`;
break;
default:
resultElement.textContent = message;
}
},
// ================== μΆ”κ°€ μ‹œμž‘ 4/4 ==================
// μ‚¬μš©μž μ •μ˜ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰
executeCustomProgram: async function(command) {
if (!this.isConnected) {
this.showCustomExecuteResult('error', '였λ₯˜: λ¨Όμ € μ„œλ²„μ— μ—°κ²°ν•΄μ•Ό ν•©λ‹ˆλ‹€.');
console.error('μ‚¬μš©μž μ •μ˜ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μ‹œλ„ 쀑 였λ₯˜: μ„œλ²„ μ—°κ²° μ•ˆλ¨');
return;
}
if (!command || command.trim() === '') {
this.showCustomExecuteResult('error', '였λ₯˜: μ‹€ν–‰ν•  λͺ…λ Ήμ–΄λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.');
console.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 || ''}`;
// 좜λ ₯ λ‚΄μš©μ΄ 있으면 μΆ”κ°€ (HTML μ•ˆμ „ 처리 포함)
if (data.output && data.output.trim()) {
successMessage += `<div class="command-output"><pre>${AppUtils.escapeHtml(data.output)}</pre></div>`;
}
this.showCustomExecuteResult('success', successMessage);
// μ‹œμŠ€ν…œ μ•Œλ¦Ό
AppUtils.addSystemNotification(`μ‚¬μš©μž μ •μ˜ λͺ…λ Ήμ–΄ μ‹€ν–‰ 성곡: ${command}`);
} else {
// μ‹€ν–‰ μ‹€νŒ¨
console.error('μ‚¬μš©μž μ •μ˜ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μ‹€νŒ¨:', data);
let errorMessage = `μ‹€ν–‰ μ‹€νŒ¨: ${data.error || 'μ•Œ 수 μ—†λŠ” 였λ₯˜'}`;
// 였λ₯˜ 좜λ ₯이 있으면 μΆ”κ°€ (HTML μ•ˆμ „ 처리 포함)
if (data.error_output && data.error_output.trim()) {
errorMessage += `<div class="command-error"><pre>${AppUtils.escapeHtml(data.error_output)}</pre></div>`;
}
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');
// HTML λ©”μ‹œμ§€ μ‚½μž… μ‹œ 주의 (innerHTML μ‚¬μš©)
resultElement.innerHTML = `<i class="fas fa-check-circle"></i> ${message}`;
break;
case 'error':
resultElement.classList.add('error');
resultElement.innerHTML = `<i class="fas fa-exclamation-circle"></i> ${message}`;
break;
case 'warning':
resultElement.classList.add('warning');
resultElement.innerHTML = `<i class="fas fa-exclamation-triangle"></i> ${message}`;
break;
case 'loading':
// λ‘œλ”© μŠ€ν”Όλ„ˆ ν•¨μˆ˜κ°€ AppUtils에 μžˆλ‹€κ³  κ°€μ •
resultElement.innerHTML = `${AppUtils.createLoadingSpinner()} ${message}`;
break;
default:
resultElement.textContent = message; // 기본은 ν…μŠ€νŠΈλ§Œ ν‘œμ‹œ
}
}
// ================== μΆ”κ°€ 끝 4/4 ====================
};
// νŽ˜μ΄μ§€ λ‘œλ“œ μ™„λ£Œ μ‹œ λͺ¨λ“ˆ μ΄ˆκΈ°ν™”
document.addEventListener('DOMContentLoaded', function() {
console.log('μž₯치 μ œμ–΄ λͺ¨λ“ˆ λ‘œλ“œλ¨');
// DOM이 μ™„μ „νžˆ λ‘œλ“œλœ ν›„ μ•½κ°„μ˜ 지연을 두고 μ΄ˆκΈ°ν™”
// DOM μš”μ†Œκ°€ ν™•μ‹€νžˆ λ‘œλ“œλœ 후에 μ΄ˆκΈ°ν™”ν•˜κΈ° μœ„ν•¨
setTimeout(() => {
// AppUtilsκ°€ μ •μ˜λ˜μ—ˆλŠ”μ§€ 확인 (μ˜μ‘΄μ„±)
if (typeof AppUtils === 'undefined') {
console.error('AppUtilsκ°€ μ •μ˜λ˜μ§€ μ•Šμ•„ DeviceControl μ΄ˆκΈ°ν™” μ‹€νŒ¨.');
// ν•„μš”ν•˜λ‹€λ©΄ μ‚¬μš©μžμ—κ²Œ μ•Œλ¦Ό ν‘œμ‹œ
alert('νŽ˜μ΄μ§€ μ΄ˆκΈ°ν™” 였λ₯˜: ν•„μˆ˜ μœ ν‹Έλ¦¬ν‹°(AppUtils)λ₯Ό λ‘œλ“œν•  수 μ—†μŠ΅λ‹ˆλ‹€.');
return;
}
DeviceControl.init();
}, 100);
});