This commit is contained in:
2025-11-29 11:13:55 +09:00
parent c0d3312bca
commit 19798cca66
12 changed files with 2094 additions and 255 deletions

View File

@@ -58,6 +58,9 @@ document.addEventListener('DOMContentLoaded', function () {
refreshServers();
loadGroups();
setupSocketIO();
// 초기 탭 설정
showTab('servers');
});
// ========================================
@@ -299,27 +302,27 @@ function closeUploadModal() {
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 {
@@ -335,9 +338,9 @@ async function startMultiUpload() {
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);
@@ -354,7 +357,7 @@ function showUploadProgress(serverIds) {
</div>
`;
}).join('');
// 요약 초기화
document.getElementById('progress-summary').innerHTML = `
<div class="summary-stats">
@@ -375,13 +378,13 @@ function hideUploadProgress() {
function setupSocketIO() {
// 업로드 진행 상황
socket.on('upload_progress', function(data) {
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 + '%';
@@ -389,18 +392,18 @@ function setupSocketIO() {
}
if (messageEl) messageEl.textContent = data.job_id ? `Job ID: ${data.job_id}` : '';
});
// 업로드 완료
socket.on('upload_complete', function(data) {
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();
});
}
@@ -412,9 +415,9 @@ function setupSocketIO() {
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">
@@ -448,30 +451,30 @@ function showResults(results, title) {
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({
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');
@@ -479,7 +482,7 @@ async function rebootSelected() {
} else {
showMessage(data.message, 'error');
}
refreshServers();
} catch (error) {
showMessage('재부팅 실패: ' + error, 'error');
@@ -499,11 +502,38 @@ function importExcel() {
// ========================================
function showMessage(message, type = 'info') {
// 간단한 알림 표시
alert(message);
// 토스트 알림으로 변경
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');
@@ -511,20 +541,20 @@ function editServer(serverId) {
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);
}
@@ -532,7 +562,7 @@ function getSelectedFirmware() {
console.error(`Server ${serverId} firmware query failed:`, error);
}
});
// 새로고침
setTimeout(() => {
refreshServers();
@@ -580,11 +610,19 @@ async function compareSelectedFirmware() {
const serverIds = getSelectedServerIds();
if (serverIds.length === 0) {
showMessage('서버를 선택하세요', 'warning');
showToast('서버를 선택하세요', 'warning');
return;
}
showMessage(`선택된 ${serverIds.length}대 서버 버전 비교 중...`, 'info');
// 비교 탭으로 전환
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', {
@@ -594,16 +632,90 @@ async function compareSelectedFirmware() {
});
const data = await res.json();
// 로딩 숨기기
if (loadingEl) loadingEl.style.display = 'none';
if (data.success) {
showResults(data.results, '버전 비교 결과');
displayComparisonResults(data.results);
showToast(`${serverIds.length}대 서버 비교 완료`, 'success');
} else {
showMessage(data.message, 'error');
if (resultEl) {
resultEl.innerHTML = `<div class="warning-text">⚠️ ${data.message}</div>`;
}
showToast(data.message, 'error');
}
} catch (error) {
showMessage('버전 비교 실패: ' + error, 'error');
if (loadingEl) loadingEl.style.display = 'none';
if (resultEl) {
resultEl.innerHTML = `<div class="warning-text">⚠️ 버전 비교 실패: ${error}</div>`;
}
showToast('버전 비교 실패: ' + error, 'error');
}
}
// ========================================
// 비교 결과 표시 (개선된 버전)
// ========================================
function displayComparisonResults(results) {
const resultEl = document.getElementById('comparison-result');
if (!resultEl) return;
if (!results || results.length === 0) {
resultEl.innerHTML = '<div class="info-text">비교 결과가 없습니다</div>';
return;
}
let html = '<div class="comparison-grid">';
results.forEach(serverResult => {
html += `
<div class="server-comparison-section">
<h3 style="margin-bottom: 15px; color: #333;">
🖥️ ${serverResult.server_name}
<span style="font-size: 0.8em; color: #666;">(${serverResult.server_ip || ''})</span>
</h3>
`;
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 += `
<div class="comparison-card ${statusClass}">
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
<strong style="font-size: 1.1em;">${comp.component_name}</strong>
<span style="font-size: 1.5em;">${statusIcon}</span>
</div>
<div style="margin-bottom: 8px;">
<span style="color: #666;">현재:</span>
<code style="background: #f0f0f0; padding: 2px 8px; border-radius: 4px;">${comp.current_version}</code>
</div>
<div style="margin-bottom: 8px;">
<span style="color: #666;">최신:</span>
<code style="background: #f0f0f0; padding: 2px 8px; border-radius: 4px;">${comp.latest_version || 'N/A'}</code>
</div>
<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #ddd; font-size: 0.9em; color: #555;">
${comp.recommendation}
</div>
</div>
`;
});
} else {
html += `<div class="warning-text">⚠️ ${serverResult.message || '비교 실패'}</div>`;
}
html += '</div>';
});
html += '</div>';
resultEl.innerHTML = html;
}
// ========================================
// 펌웨어 버전 추가 모달
// ========================================
@@ -681,7 +793,7 @@ async function refreshFirmwareVersionList() {
// Dell Catalog에서 최신 버전 자동 가져오기
// ========================================
async function syncDellCatalog(model = "PowerEdge R750") {
showMessage(`${model} 최신 버전 정보를 Dell에서 가져오는 중...`, "info");
showToast(`${model} 최신 버전 정보를 Dell에서 가져오는 중...`, "info");
try {
const response = await fetchWithCSRF("/catalog/sync", {
@@ -692,13 +804,95 @@ async function syncDellCatalog(model = "PowerEdge R750") {
const data = await response.json();
if (data.success) {
showMessage(data.message, "success");
showToast(data.message, "success");
await refreshFirmwareVersionList();
} else {
showMessage(data.message, "error");
showToast(data.message, "error");
}
} catch (error) {
showMessage("버전 정보 동기화 실패: " + error, "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');
}
}
@@ -716,8 +910,8 @@ async function refreshFirmwareVersionList() {
tbody.innerHTML = data.versions.length
? data.versions
.map(
(v) => `
.map(
(v) => `
<tr>
<td>${v.component_name}</td>
<td>${v.latest_version}</td>
@@ -728,13 +922,79 @@ async function refreshFirmwareVersionList() {
<button class="btn btn-danger" onclick="deleteFirmwareVersion(${v.id})">삭제</button>
</td>
</tr>`
)
.join("")
)
.join("")
: `<tr><td colspan="6" class="empty-message">등록된 버전 정보가 없습니다</td></tr>`;
} else {
showMessage(data.message, "error");
showToast(data.message, "error");
}
} catch (error) {
showMessage("버전 목록 로드 실패: " + error, "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');
}
}