/** * Dell iDRAC 멀티 서버 펌웨어 관리 JavaScript * backend/static/js/idrac_main.js */ // SocketIO 연결 const socket = io(); // 전역 변수 let servers = []; let selectedServers = new Set(); // CSRF 토큰 가져오기 function getCSRFToken() { const metaTag = document.querySelector('meta[name="csrf-token"]'); if (metaTag) return metaTag.getAttribute('content'); const cookies = document.cookie.split(';'); for (let cookie of cookies) { const [name, value] = cookie.trim().split('='); if (name === 'csrf_token') return value; } return null; } // fetch 래퍼 함수 async function fetchWithCSRF(url, options = {}) { const csrfToken = getCSRFToken(); if (!options.headers) options.headers = {}; if (csrfToken) { options.headers['X-CSRFToken'] = csrfToken; options.headers['X-CSRF-Token'] = csrfToken; } if (options.body) { if (options.body instanceof FormData) { // 자동 처리 } else if (typeof options.body === 'string') { if (!options.headers['Content-Type']) { options.headers['Content-Type'] = 'application/json'; } } else if (typeof options.body === 'object') { options.headers['Content-Type'] = 'application/json'; options.body = JSON.stringify(options.body); } } return fetch(url, options); } // ======================================== // 초기화 // ======================================== document.addEventListener('DOMContentLoaded', function () { console.log('iDRAC 멀티 서버 관리 시스템 시작'); refreshServers(); loadGroups(); setupSocketIO(); // 초기 탭 설정 showTab('servers'); }); // ======================================== // 서버 관리 // ======================================== async function refreshServers() { try { const group = document.getElementById('group-filter').value; const url = `/idrac/api/servers${group !== 'all' ? '?group=' + group : ''}`; const response = await fetchWithCSRF(url); const data = await response.json(); if (data.success) { servers = data.servers; renderServerList(); updateServerCount(); } else { showMessage(data.message, 'error'); } } catch (error) { showMessage('서버 목록 로드 실패: ' + error, 'error'); } } function renderServerList() { const tbody = document.getElementById('server-list'); if (servers.length === 0) { tbody.innerHTML = '등록된 서버가 없습니다'; return; } tbody.innerHTML = servers.map(server => ` ${server.name} ${server.ip_address} ${server.group_name || '-'} ${getStatusText(server.status)} ${server.current_bios || '-'} ${server.last_connected ? formatDateTime(server.last_connected) : '-'} `).join(''); } function getStatusText(status) { const map = { registered: '등록됨', online: '온라인', offline: '오프라인', updating: '업데이트중' }; return map[status] || status; } function formatDateTime(dateStr) { const date = new Date(dateStr); return date.toLocaleString('ko-KR', { year: '2-digit', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } function updateServerCount() { document.getElementById('server-count').textContent = `${servers.length}대`; } // ======================================== // 서버 선택 // ======================================== function toggleSelectAll() { const checkbox = document.getElementById('select-all'); const checkboxes = document.querySelectorAll('.server-checkbox'); selectedServers.clear(); checkboxes.forEach(cb => { cb.checked = checkbox.checked; if (checkbox.checked) selectedServers.add(parseInt(cb.value)); }); updateSelectedCount(); } function toggleServerSelection(serverId) { if (selectedServers.has(serverId)) selectedServers.delete(serverId); else selectedServers.add(serverId); updateSelectedCount(); const all = document.querySelectorAll('.server-checkbox'); const checked = document.querySelectorAll('.server-checkbox:checked'); document.getElementById('select-all').checked = all.length === checked.length; } function updateSelectedCount() { document.getElementById('selected-count').textContent = `선택: ${selectedServers.size}대`; } function getSelectedServerIds() { return Array.from(selectedServers); } // ======================================== // 그룹 관리 // ======================================== async function loadGroups() { try { const response = await fetchWithCSRF('/idrac/api/groups'); const data = await response.json(); if (data.success) { const select = document.getElementById('group-filter'); const currentValue = select.value; select.innerHTML = ''; data.groups.forEach(g => select.innerHTML += ``); if (currentValue) select.value = currentValue; } } catch (e) { console.error('그룹 로드 실패:', e); } } function filterByGroup() { refreshServers(); } // ======================================== // 서버 추가/수정/삭제 // ======================================== function showAddServerModal() { document.getElementById('add-server-modal').style.display = 'flex'; } function closeAddServerModal() { const modal = document.getElementById('add-server-modal'); if (modal) modal.style.display = 'none'; setTimeout(() => clearServerForm(), 0); // DOM 안정 후 폼 초기화 } function clearServerForm() { const fieldIds = [ 'server-name', 'server-ip', 'server-username', 'server-password', 'server-group', 'server-model' ]; fieldIds.forEach(id => { const el = document.getElementById(id); if (el) { if (el.type === 'checkbox') el.checked = false; else if (el.type === 'file') el.value = null; else el.value = ''; } else { console.warn(`[clearServerForm] 요소 없음: #${id}`); } }); } async function addServer() { const serverData = { name: document.getElementById('server-name').value, ip_address: document.getElementById('server-ip').value, username: document.getElementById('server-username').value, password: document.getElementById('server-password').value, group_name: document.getElementById('server-group').value, model: document.getElementById('server-model').value }; if (!serverData.name || !serverData.ip_address || !serverData.password) { showMessage('필수 필드를 입력하세요 (서버명, IP, 비밀번호)', 'error'); return; } try { const response = await fetchWithCSRF('/idrac/api/servers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(serverData) }); const data = await response.json(); if (data.success) { showMessage(data.message, 'success'); closeAddServerModal(); refreshServers(); loadGroups(); } else showMessage(data.message, 'error'); } catch (e) { showMessage('서버 추가 실패: ' + e, 'error'); } } async function deleteServer(serverId) { if (!confirm('이 서버를 삭제하시겠습니까?')) return; try { const res = await fetchWithCSRF(`/idrac/api/servers/${serverId}`, { method: 'DELETE' }); const data = await res.json(); if (data.success) { showMessage(data.message, 'success'); refreshServers(); } else showMessage(data.message, 'error'); } catch (e) { showMessage('서버 삭제 실패: ' + e, 'error'); } } // ======================================== // 연결 테스트 // ======================================== async function testConnection(serverId) { try { const res = await fetchWithCSRF(`/idrac/api/servers/${serverId}/test`, { method: 'POST' }); const data = await res.json(); showMessage(data.message, data.success ? 'success' : 'error'); refreshServers(); } catch (e) { showMessage('연결 테스트 실패: ' + e, 'error'); } } // ======================================== // 펌웨어 업로드 // ======================================== function showUploadModal() { const ids = getSelectedServerIds(); if (ids.length === 0) return showMessage('서버를 선택하세요', 'warning'); document.getElementById('upload-server-count').textContent = `${ids.length}대`; document.getElementById('upload-modal').style.display = 'flex'; } function closeUploadModal() { document.getElementById('upload-modal').style.display = 'none'; document.getElementById('firmware-file').value = ''; } async function startMultiUpload() { const fileInput = document.getElementById('firmware-file'); const serverIds = getSelectedServerIds(); if (!fileInput.files[0]) { showMessage('파일을 선택하세요', 'warning'); return; } const formData = new FormData(); formData.append('file', fileInput.files[0]); formData.append('server_ids', serverIds.join(',')); try { closeUploadModal(); showUploadProgress(serverIds); const response = await fetchWithCSRF('/idrac/api/upload-multi', { method: 'POST', body: formData }); const data = await response.json(); if (data.success) { showMessage(data.message, 'success'); } else { showMessage(data.message, 'error'); hideUploadProgress(); } } catch (error) { showMessage('업로드 시작 실패: ' + error, 'error'); hideUploadProgress(); } } function showUploadProgress(serverIds) { const section = document.getElementById('upload-progress-section'); const list = document.getElementById('upload-progress-list'); section.style.display = 'block'; // 각 서버별 진행 바 생성 list.innerHTML = serverIds.map(id => { const server = servers.find(s => s.id === id); return `
${server.name} (${server.ip_address}) 대기중...
`; }).join(''); // 요약 초기화 document.getElementById('progress-summary').innerHTML = `
전체: ${serverIds.length}대 완료: 0대 실패: 0대
`; } function hideUploadProgress() { document.getElementById('upload-progress-section').style.display = 'none'; } // ======================================== // SocketIO 이벤트 // ======================================== function setupSocketIO() { // 업로드 진행 상황 socket.on('upload_progress', function (data) { console.log('Upload progress:', data); const statusEl = document.getElementById(`status-${data.server_id}`); const barEl = document.getElementById(`bar-${data.server_id}`); const messageEl = document.getElementById(`message-${data.server_id}`); if (statusEl) statusEl.textContent = data.message; if (barEl) { barEl.style.width = data.progress + '%'; barEl.className = `progress-bar progress-${data.status}`; } if (messageEl) messageEl.textContent = data.job_id ? `Job ID: ${data.job_id}` : ''; }); // 업로드 완료 socket.on('upload_complete', function (data) { console.log('Upload complete:', data); const summary = data.summary; document.getElementById('completed-count').textContent = `완료: ${summary.success}대`; document.getElementById('failed-count').textContent = `실패: ${summary.failed}대`; showMessage(`업로드 완료: 성공 ${summary.success}대, 실패 ${summary.failed}대`, 'success'); showResults(data.results, '업로드 결과'); refreshServers(); }); } // ======================================== // 결과 표시 // ======================================== function showResults(results, title) { const section = document.getElementById('result-section'); const content = document.getElementById('result-content'); section.style.display = 'block'; content.innerHTML = `

${title}

${results.map(r => ` `).join('')}
서버명 상태 메시지
${r.server_name} ${r.success ? '✓ 성공' : '✗ 실패'} ${r.message}${r.job_id ? ` (Job: ${r.job_id})` : ''}
`; } // ======================================== // 재부팅 // ======================================== async function rebootSelected() { const serverIds = getSelectedServerIds(); if (serverIds.length === 0) { showMessage('서버를 선택하세요', 'warning'); return; } if (!confirm(`선택한 ${serverIds.length}대 서버를 재부팅하시겠습니까?\n\n⚠️ 업로드된 펌웨어가 적용됩니다!`)) { return; } try { const response = await fetchWithCSRF('/idrac/api/servers/reboot-multi', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ server_ids: serverIds, type: 'GracefulRestart' }) }); const data = await response.json(); if (data.success) { const summary = data.summary; showMessage(`재부팅 시작: 성공 ${summary.success}대, 실패 ${summary.failed}대`, 'success'); showResults(data.results, '재부팅 결과'); } else { showMessage(data.message, 'error'); } refreshServers(); } catch (error) { showMessage('재부팅 실패: ' + error, 'error'); } } // ======================================== // Excel 가져오기 (추후 구현) // ======================================== function importExcel() { showMessage('Excel 가져오기 기능은 개발 중입니다', 'info'); } // ======================================== // 유틸리티 // ======================================== function showMessage(message, type = 'info') { // 토스트 알림으로 변경 showToast(message, type); console.log(`[${type}] ${message}`); } // ======================================== // 토스트 알림 시스템 // ======================================== function showToast(message, type = 'info') { // 기존 토스트 제거 const existingToast = document.querySelector('.toast'); if (existingToast) { existingToast.remove(); } // 새 토스트 생성 const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.textContent = message; document.body.appendChild(toast); // 애니메이션 setTimeout(() => toast.classList.add('show'), 100); // 3초 후 제거 setTimeout(() => { toast.classList.remove('show'); setTimeout(() => toast.remove(), 300); }, 3000); } // 편의 함수들 function editServer(serverId) { showMessage('서버 수정 기능은 개발 중입니다', 'info'); } function getSelectedFirmware() { const serverIds = getSelectedServerIds(); if (serverIds.length === 0) { showMessage('서버를 선택하세요', 'warning'); return; } showMessage('선택한 서버의 펌웨어 조회 중...', 'info'); // 각 서버별로 펌웨어 조회 serverIds.forEach(async (serverId) => { try { const response = await fetchWithCSRF(`/idrac/api/servers/${serverId}/firmware`); const data = await response.json(); if (data.success) { console.log(`Server ${serverId} firmware:`, data.data); } } catch (error) { console.error(`Server ${serverId} firmware query failed:`, error); } }); // 새로고침 setTimeout(() => { refreshServers(); }, 2000); } // ======================================== // 다중 연결 테스트 // ======================================== async function testSelectedConnections() { const serverIds = getSelectedServerIds(); if (serverIds.length === 0) { showMessage('서버를 선택하세요', 'warning'); return; } showMessage(`선택된 ${serverIds.length}대 서버 연결 테스트 중...`, 'info'); try { const res = await fetchWithCSRF('/idrac/api/servers/test-multi', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ server_ids: serverIds }) }); const data = await res.json(); if (data.success) { const summary = data.summary; showMessage(`연결 성공: ${summary.success}대 / 실패: ${summary.failed}대`, 'success'); showResults(data.results, '연결 테스트 결과'); } else { showMessage(data.message, 'error'); } refreshServers(); } catch (error) { showMessage('연결 테스트 실패: ' + error, 'error'); } } // ======================================== // 다중 펌웨어 버전 비교 // ======================================== async function compareSelectedFirmware() { const serverIds = getSelectedServerIds(); if (serverIds.length === 0) { showToast('서버를 선택하세요', 'warning'); return; } // 비교 탭으로 전환 showTab('comparison'); // 로딩 표시 const loadingEl = document.getElementById('comparison-loading'); const resultEl = document.getElementById('comparison-result'); if (loadingEl) loadingEl.style.display = 'block'; if (resultEl) resultEl.innerHTML = ''; try { const res = await fetchWithCSRF('/idrac/api/servers/firmware/compare-multi', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ server_ids: serverIds }) }); const data = await res.json(); // 로딩 숨기기 if (loadingEl) loadingEl.style.display = 'none'; if (data.success) { displayComparisonResults(data.results); showToast(`${serverIds.length}대 서버 비교 완료`, 'success'); } else { if (resultEl) { resultEl.innerHTML = `
⚠️ ${data.message}
`; } showToast(data.message, 'error'); } } catch (error) { if (loadingEl) loadingEl.style.display = 'none'; if (resultEl) { resultEl.innerHTML = `
⚠️ 버전 비교 실패: ${error}
`; } showToast('버전 비교 실패: ' + error, 'error'); } } // ======================================== // 비교 결과 표시 (개선된 버전) // ======================================== function displayComparisonResults(results) { const resultEl = document.getElementById('comparison-result'); if (!resultEl) return; if (!results || results.length === 0) { resultEl.innerHTML = '
비교 결과가 없습니다
'; return; } let html = '
'; results.forEach(serverResult => { html += `

🖥️ ${serverResult.server_name} (${serverResult.server_ip || ''})

`; if (serverResult.success && serverResult.comparisons) { serverResult.comparisons.forEach(comp => { const statusClass = comp.status === 'outdated' ? 'outdated' : comp.status === 'latest' ? 'latest' : 'unknown'; const statusIcon = comp.status === 'outdated' ? '⚠️' : comp.status === 'latest' ? '✅' : '❓'; html += `
${comp.component_name} ${statusIcon}
현재: ${comp.current_version}
최신: ${comp.latest_version || 'N/A'}
${comp.recommendation}
`; }); } else { html += `
⚠️ ${serverResult.message || '비교 실패'}
`; } html += '
'; }); html += '
'; resultEl.innerHTML = html; } // ======================================== // 펌웨어 버전 추가 모달 // ======================================== function showAddVersionModal() { const modal = document.getElementById('add-version-modal'); if (modal) modal.style.display = 'flex'; } function closeAddVersionModal() { const modal = document.getElementById('add-version-modal'); if (modal) modal.style.display = 'none'; } async function addFirmwareVersion() { const data = { component_name: document.getElementById('version-component').value, latest_version: document.getElementById('version-latest').value, server_model: document.getElementById('version-model').value, vendor: document.getElementById('version-vendor').value, release_date: document.getElementById('version-release-date').value, download_url: document.getElementById('version-download-url').value, notes: document.getElementById('version-notes').value, is_critical: document.getElementById('version-critical').checked }; if (!data.component_name || !data.latest_version) { showMessage('컴포넌트명과 버전을 입력하세요', 'warning'); return; } try { const response = await fetchWithCSRF('/idrac/api/firmware-versions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); if (result.success) { showMessage(result.message, 'success'); closeAddVersionModal(); refreshFirmwareVersionList(); } else { showMessage(result.message, 'error'); } } catch (error) { showMessage('버전 추가 실패: ' + error, 'error'); } } async function refreshFirmwareVersionList() { try { const response = await fetchWithCSRF('/idrac/api/firmware-versions'); const data = await response.json(); if (data.success) { const tbody = document.getElementById('version-list'); tbody.innerHTML = data.versions.map(v => ` ${v.component_name} ${v.latest_version} ${v.server_model || '-'} ${v.release_date || '-'} ${v.is_critical ? '⚠️' : ''} `).join(''); } } catch (error) { showMessage('버전 목록 로드 실패: ' + error, 'error'); } } // ======================================== // Dell Catalog에서 최신 버전 자동 가져오기 // ======================================== async function syncDellCatalog(model = "PowerEdge R750") { showToast(`${model} 최신 버전 정보를 Dell에서 가져오는 중...`, "info"); try { const response = await fetchWithCSRF("/catalog/sync", { method: "POST", body: { model } }); const data = await response.json(); if (data.success) { showToast(data.message, "success"); await refreshFirmwareVersionList(); } else { showToast(data.message, "error"); } } catch (error) { showToast("버전 정보 동기화 실패: " + error, "error"); } } // ======================================== // DRM 리포지토리 동기화 // ======================================== async function syncFromDRM() { // DRM 리포지토리 경로 입력 받기 const repositoryPath = prompt( 'DRM 리포지토리 경로를 입력하세요:\n예: C:\\Dell\\Repository 또는 \\\\network\\share\\DellRepo', 'C:\\Dell\\Repository' ); if (!repositoryPath) return; const model = prompt('서버 모델을 입력하세요:', 'PowerEdge R750'); if (!model) return; showToast('DRM 리포지토리에서 펌웨어 정보 가져오는 중...', 'info'); try { const response = await fetchWithCSRF('/drm/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ repository_path: repositoryPath, model: model }) }); const data = await response.json(); if (data.success) { showToast(`${data.message}`, 'success'); await refreshFirmwareVersionList(); } else { showToast(data.message, 'error'); } } catch (error) { showToast('DRM 동기화 실패: ' + error, 'error'); } } async function checkDRMRepository() { const repositoryPath = prompt( 'DRM 리포지토리 경로를 입력하세요:', 'C:\\Dell\\Repository' ); if (!repositoryPath) return; try { const response = await fetchWithCSRF('/drm/check', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ repository_path: repositoryPath }) }); const data = await response.json(); if (data.success && data.info) { const info = data.info; let message = `DRM 리포지토리 정보:\n\n`; message += `경로: ${info.path}\n`; message += `카탈로그 파일: ${info.catalog_file || '없음'}\n`; message += `총 패키지 수: ${info.total_packages || 0}\n`; if (info.available_models && info.available_models.length > 0) { message += `\n사용 가능한 모델 (${info.available_models.length}개):\n`; message += info.available_models.slice(0, 10).join(', '); if (info.available_models.length > 10) { message += ` ... 외 ${info.available_models.length - 10}개`; } } alert(message); } else { showToast('DRM 리포지토리를 찾을 수 없습니다', 'error'); } } catch (error) { showToast('DRM 확인 실패: ' + error, 'error'); } } // ======================================== // 펌웨어 버전 목록 새로고침 // ======================================== async function refreshFirmwareVersionList() { try { const response = await fetchWithCSRF("/idrac/api/firmware-versions"); const data = await response.json(); if (data.success) { const tbody = document.getElementById("version-list"); if (!tbody) return; tbody.innerHTML = data.versions.length ? data.versions .map( (v) => ` ${v.component_name} ${v.latest_version} ${v.server_model || "-"} ${v.release_date || "-"} ${v.is_critical ? "⚠️" : ""} ` ) .join("") : `등록된 버전 정보가 없습니다`; } else { showToast(data.message, "error"); } } catch (error) { showToast("버전 목록 로드 실패: " + error, "error"); } } // ======================================== // 탭 전환 기능 // ======================================== function showTab(tabName) { // 모든 탭 콘텐츠 숨기기 document.querySelectorAll('.tab-content').forEach(tab => { tab.classList.remove('active'); }); // 모든 탭 버튼 비활성화 document.querySelectorAll('.tab-button').forEach(btn => { btn.classList.remove('active'); }); // 선택된 탭 표시 const selectedTab = document.getElementById(tabName + '-tab'); if (selectedTab) { selectedTab.classList.add('active'); } // 클릭된 버튼 활성화 (이벤트에서 호출된 경우) if (event && event.target) { event.target.classList.add('active'); } else { // 직접 호출된 경우 해당 버튼 찾아서 활성화 const buttons = document.querySelectorAll('.tab-button'); buttons.forEach((btn, index) => { if ((tabName === 'servers' && index === 0) || (tabName === 'versions' && index === 1) || (tabName === 'comparison' && index === 2)) { btn.classList.add('active'); } }); } // 버전 탭 선택 시 목록 로드 if (tabName === 'versions') { refreshFirmwareVersionList(); } } // ======================================== // 펌웨어 버전 삭제 // ======================================== async function deleteFirmwareVersion(versionId) { if (!confirm('이 펌웨어 버전 정보를 삭제하시겠습니까?')) return; try { const response = await fetchWithCSRF(`/idrac/api/firmware-versions/${versionId}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { showToast(data.message, 'success'); refreshFirmwareVersionList(); } else { showToast(data.message, 'error'); } } catch (error) { showToast('삭제 실패: ' + error, 'error'); } }