741 lines
24 KiB
JavaScript
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");
|
|
}
|
|
}
|