Files
iDRAC_Info/backend/static/js/idrac_main.js
2025-10-21 20:29:39 +09:00

741 lines
24 KiB
JavaScript

/**
* 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();
});
// ========================================
// 서버 관리
// ========================================
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 = '<tr><td colspan="8" class="empty-message">등록된 서버가 없습니다</td></tr>';
return;
}
tbody.innerHTML = servers.map(server => `
<tr data-server-id="${server.id}">
<td><input type="checkbox" class="server-checkbox" value="${server.id}"
${selectedServers.has(server.id) ? 'checked' : ''}
onchange="toggleServerSelection(${server.id})"></td>
<td><strong>${server.name}</strong></td>
<td><code>${server.ip_address}</code></td>
<td><span class="badge">${server.group_name || '-'}</span></td>
<td><span class="status status-${server.status}">${getStatusText(server.status)}</span></td>
<td>${server.current_bios || '-'}</td>
<td>${server.last_connected ? formatDateTime(server.last_connected) : '-'}</td>
<td class="action-buttons">
<button onclick="testConnection(${server.id})" class="btn-icon" title="연결 테스트">🔌</button>
<button onclick="editServer(${server.id})" class="btn-icon" title="수정">✏️</button>
<button onclick="deleteServer(${server.id})" class="btn-icon" title="삭제">🗑️</button>
</td>
</tr>
`).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 = '<option value="all">전체</option>';
data.groups.forEach(g => select.innerHTML += `<option value="${g}">${g}</option>`);
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 `
<div class="progress-item" id="progress-${id}">
<div class="progress-header">
<strong>${server.name}</strong> (${server.ip_address})
<span class="progress-status" id="status-${id}">대기중...</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar" id="bar-${id}" style="width: 0%"></div>
</div>
<div class="progress-message" id="message-${id}"></div>
</div>
`;
}).join('');
// 요약 초기화
document.getElementById('progress-summary').innerHTML = `
<div class="summary-stats">
<span>전체: ${serverIds.length}대</span>
<span id="completed-count">완료: 0대</span>
<span id="failed-count">실패: 0대</span>
</div>
`;
}
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 = `
<h3>${title}</h3>
<table class="result-table">
<thead>
<tr>
<th>서버명</th>
<th>상태</th>
<th>메시지</th>
</tr>
</thead>
<tbody>
${results.map(r => `
<tr class="${r.success ? 'success' : 'failed'}">
<td><strong>${r.server_name}</strong></td>
<td>
<span class="result-badge ${r.success ? 'badge-success' : 'badge-failed'}">
${r.success ? '✓ 성공' : '✗ 실패'}
</span>
</td>
<td>${r.message}${r.job_id ? ` (Job: ${r.job_id})` : ''}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
// ========================================
// 재부팅
// ========================================
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') {
// 간단한 알림 표시
alert(message);
console.log(`[${type}] ${message}`);
}
// 편의 함수들
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) {
showMessage('서버를 선택하세요', 'warning');
return;
}
showMessage(`선택된 ${serverIds.length}대 서버 버전 비교 중...`, 'info');
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 (data.success) {
showResults(data.results, '버전 비교 결과');
} else {
showMessage(data.message, 'error');
}
} catch (error) {
showMessage('버전 비교 실패: ' + error, 'error');
}
}
// ========================================
// 펌웨어 버전 추가 모달
// ========================================
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 => `
<tr>
<td>${v.component_name}</td>
<td>${v.latest_version}</td>
<td>${v.server_model || '-'}</td>
<td>${v.release_date || '-'}</td>
<td>${v.is_critical ? '⚠️' : ''}</td>
<td><button class="btn btn-danger" onclick="deleteFirmwareVersion(${v.id})">삭제</button></td>
</tr>
`).join('');
}
} catch (error) {
showMessage('버전 목록 로드 실패: ' + error, 'error');
}
}
// ========================================
// Dell Catalog에서 최신 버전 자동 가져오기
// ========================================
async function syncDellCatalog(model = "PowerEdge R750") {
showMessage(`${model} 최신 버전 정보를 Dell에서 가져오는 중...`, "info");
try {
const response = await fetchWithCSRF("/catalog/sync", {
method: "POST",
body: { model }
});
const data = await response.json();
if (data.success) {
showMessage(data.message, "success");
await refreshFirmwareVersionList();
} else {
showMessage(data.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");
if (!tbody) return;
tbody.innerHTML = data.versions.length
? data.versions
.map(
(v) => `
<tr>
<td>${v.component_name}</td>
<td>${v.latest_version}</td>
<td>${v.server_model || "-"}</td>
<td>${v.release_date || "-"}</td>
<td>${v.is_critical ? "⚠️" : ""}</td>
<td>
<button class="btn btn-danger" onclick="deleteFirmwareVersion(${v.id})">삭제</button>
</td>
</tr>`
)
.join("")
: `<tr><td colspan="6" class="empty-message">등록된 버전 정보가 없습니다</td></tr>`;
} else {
showMessage(data.message, "error");
}
} catch (error) {
showMessage("버전 목록 로드 실패: " + error, "error");
}
}