Initial project upload
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
let allServers = [];
|
||||
let analysisData = {};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadServers();
|
||||
|
||||
document.getElementById('check-all').addEventListener('change', (e) => {
|
||||
const checked = e.target.checked;
|
||||
document.querySelectorAll('.server-checkbox').forEach(cb => cb.checked = checked);
|
||||
});
|
||||
});
|
||||
|
||||
async function loadServers() {
|
||||
try {
|
||||
const res = await axios.get('/idrac/api/servers');
|
||||
if (res.data.success) {
|
||||
allServers = res.data.servers;
|
||||
renderServerList(allServers);
|
||||
} else {
|
||||
alert('서버 목록 로드 실패: ' + res.data.message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('서버 목록 로드 중 오류가 발생했습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
function renderServerList(servers) {
|
||||
const tbody = document.querySelector('#selection-table tbody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (servers.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="5">등록된 서버가 없습니다.</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
servers.forEach(server => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td><input type="checkbox" class="server-checkbox" value="${server.id}"></td>
|
||||
<td>${server.name}</td>
|
||||
<td>${server.ip_address}</td>
|
||||
<td>${server.model || '-'}</td>
|
||||
<td>${server.status || 'Unknown'}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 시작: 서버 선택 후 동기화 (iDRAC -> DB)
|
||||
async function syncSelectedServers() {
|
||||
const selectedIds = Array.from(document.querySelectorAll('.server-checkbox:checked')).map(cb => parseInt(cb.value));
|
||||
|
||||
if (selectedIds.length === 0) {
|
||||
alert('동기화할 서버를 하나 이상 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`선택한 ${selectedIds.length}대 서버의 설정을 iDRAC에서 가져와 DB에 저장하시겠습니까?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('loading').style.display = 'flex';
|
||||
updateLoadingMessage('설정 데이터 동기화 중...');
|
||||
|
||||
try {
|
||||
const res = await axios.post('/server-config/api/sync', {
|
||||
server_ids: selectedIds,
|
||||
categories: ['bios', 'system', 'idrac']
|
||||
});
|
||||
|
||||
if (res.data.success) {
|
||||
const failed = res.data.results.failed;
|
||||
if (failed.length > 0) {
|
||||
const failedNames = failed.map(f => f.name || f.id).join(', ');
|
||||
alert(`일부 서버 동기화 실패:\n${failedNames}\n\n성공한 서버는 DB에 저장되었습니다.`);
|
||||
} else {
|
||||
alert('모든 서버 동기화가 완료되었습니다. 이제 분석을 수행할 수 있습니다.');
|
||||
}
|
||||
|
||||
// 만약 이미 결과 화면이 보이고 있다면 자동 갱신
|
||||
if (document.getElementById('result-card').style.display !== 'none') {
|
||||
startAnalysis();
|
||||
}
|
||||
} else {
|
||||
alert('동기화 실패: ' + res.data.message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('동기화 요청 중 오류가 발생했습니다: ' + e.message);
|
||||
} finally {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 시작: DB 데이터 기반 분석 (DB -> Web)
|
||||
async function startAnalysis() {
|
||||
const selectedIds = Array.from(document.querySelectorAll('.server-checkbox:checked')).map(cb => parseInt(cb.value));
|
||||
|
||||
if (selectedIds.length === 0) {
|
||||
alert('분석할 서버를 하나 이상 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('loading').style.display = 'flex';
|
||||
document.getElementById('result-card').style.display = 'none';
|
||||
updateLoadingMessage('데이터 분석 중...');
|
||||
|
||||
try {
|
||||
const res = await axios.post('/server-config/api/fetch', {
|
||||
server_ids: selectedIds,
|
||||
categories: ['bios', 'system', 'idrac']
|
||||
});
|
||||
|
||||
if (res.data.success) {
|
||||
analysisData = res.data.results;
|
||||
renderComparison();
|
||||
document.getElementById('result-card').style.display = 'block';
|
||||
} else {
|
||||
alert('데이터 조회 실패: ' + res.data.message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('데이터 조회 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function updateLoadingMessage(msg) {
|
||||
const loadingText = document.getElementById('loading-text');
|
||||
if (loadingText) loadingText.textContent = msg;
|
||||
}
|
||||
|
||||
function renderComparison() {
|
||||
const container = document.getElementById('result-container');
|
||||
const showDiffOnly = document.getElementById('show-diff-only').checked;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
const serverIds = Object.keys(analysisData);
|
||||
if (serverIds.length === 0) {
|
||||
container.innerHTML = '<p>데이터가 없습니다.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 통합된 모든 키(Attribute Name) 수집
|
||||
const allKeys = new Set();
|
||||
const serverNames = {};
|
||||
const serverUpdated = {}; // 마지막 업데이트 시간
|
||||
|
||||
serverIds.forEach(id => {
|
||||
const data = analysisData[id];
|
||||
serverNames[id] = data.name;
|
||||
|
||||
// 가장 최근 업데이트 시간 찾기
|
||||
const times = [data.bios_updated, data.idrac_updated, data.system_updated].filter(t => t);
|
||||
if (times.length > 0) {
|
||||
times.sort();
|
||||
serverUpdated[id] = times[times.length - 1]; // 가장 최신
|
||||
} else {
|
||||
serverUpdated[id] = 'N/A';
|
||||
}
|
||||
|
||||
// BIOS
|
||||
if (data.bios) Object.keys(data.bios).forEach(k => allKeys.add('BIOS: ' + k));
|
||||
// iDRAC
|
||||
if (data.idrac) Object.keys(data.idrac).forEach(k => allKeys.add('iDRAC: ' + k));
|
||||
// System
|
||||
if (data.system) Object.keys(flattenObject(data.system)).forEach(k => allKeys.add('System: ' + k));
|
||||
});
|
||||
|
||||
const sortedKeys = Array.from(allKeys).sort();
|
||||
|
||||
// 테이블 생성
|
||||
const table = document.createElement('table');
|
||||
table.className = 'comparison-table';
|
||||
|
||||
// 헤더
|
||||
const thead = document.createElement('thead');
|
||||
let headerHtml = '<tr><th style="min-width: 200px;">Attribute</th>';
|
||||
serverIds.forEach(id => {
|
||||
headerHtml += `<th>${serverNames[id]}</th>`;
|
||||
});
|
||||
headerHtml += '</tr>';
|
||||
|
||||
// 업데이트 시간 행 추가
|
||||
headerHtml += '<tr style="background-color: #fafafa; font-size: 0.8em; color: #666;"><th>Local DB Updated</th>';
|
||||
serverIds.forEach(id => {
|
||||
const timeStr = serverUpdated[id] !== 'N/A' ? new Date(serverUpdated[id]).toLocaleString() : 'Never';
|
||||
headerHtml += `<th>${timeStr}</th>`;
|
||||
});
|
||||
headerHtml += '</tr>';
|
||||
|
||||
thead.innerHTML = headerHtml;
|
||||
table.appendChild(thead);
|
||||
|
||||
// 바디
|
||||
const tbody = document.createElement('tbody');
|
||||
let hasDiff = false;
|
||||
|
||||
sortedKeys.forEach(key => {
|
||||
const row = document.createElement('tr');
|
||||
let rowHtml = `<td>${key}</td>`;
|
||||
|
||||
// 값 비교를 위한 배열
|
||||
const values = [];
|
||||
|
||||
serverIds.forEach(id => {
|
||||
const val = getValueByKey(analysisData[id], key);
|
||||
values.push(val);
|
||||
});
|
||||
|
||||
// 값들이 모두 같은지 확인
|
||||
const firstVal = values[0];
|
||||
const isDiff = !values.every(v => v === firstVal);
|
||||
|
||||
if (isDiff) {
|
||||
row.classList.add('diff-row');
|
||||
hasDiff = true;
|
||||
}
|
||||
|
||||
if (showDiffOnly && !isDiff) {
|
||||
return; // Diff Only 모드이고 차이가 없으면 건너뜀
|
||||
}
|
||||
|
||||
values.forEach(val => {
|
||||
const displayVal = (val === undefined || val === null) ? '<span style="color:#ccc">N/A</span>' : val;
|
||||
const cellClass = (isDiff && val !== firstVal) ? 'diff-highlight' : ''; // 첫번째 서버(기준)와 다르면 강조
|
||||
rowHtml += `<td class="${cellClass}">${displayVal}</td>`;
|
||||
});
|
||||
|
||||
row.innerHTML = rowHtml;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
if (showDiffOnly && !hasDiff) {
|
||||
// 차이점이 하나도 없는데 차이점만 보기 체크된 경우 빈 row가 아니라 메시지 표시가 나을지 고민
|
||||
// tbody가 비어있음
|
||||
}
|
||||
|
||||
if (tbody.children.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="' + (serverIds.length + 1) + '">표시할 항목이 없습니다.</td></tr>';
|
||||
}
|
||||
|
||||
table.appendChild(tbody);
|
||||
container.appendChild(table);
|
||||
}
|
||||
|
||||
// 헬퍼: 키(Category: Name)로 값 추출
|
||||
function getValueByKey(data, compositeKey) {
|
||||
if (!data) return undefined;
|
||||
|
||||
const [category, ...rest] = compositeKey.split(': ');
|
||||
const realKey = rest.join(': '); // 혹시 키 안에 콜론이 있을 경우 복구
|
||||
|
||||
if (category === 'BIOS') return data.bios ? data.bios[realKey] : undefined;
|
||||
if (category === 'iDRAC') return data.idrac ? data.idrac[realKey] : undefined;
|
||||
if (category === 'System') {
|
||||
const flatSys = flattenObject(data.system);
|
||||
return flatSys ? flatSys[realKey] : undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 객체 평탄화 (Nested Object -> Flat Key)
|
||||
function flattenObject(obj, prefix = '') {
|
||||
if (!obj) return {};
|
||||
return Object.keys(obj).reduce((acc, k) => {
|
||||
const pre = prefix.length ? prefix + '.' : '';
|
||||
if (typeof obj[k] === 'object' && obj[k] !== null) {
|
||||
Object.assign(acc, flattenObject(obj[k], pre + k));
|
||||
} else {
|
||||
acc[pre + k] = obj[k];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function downloadReport() {
|
||||
const table = document.querySelector('.comparison-table');
|
||||
if (!table) {
|
||||
alert('다운로드할 데이터가 없습니다. 먼저 분석을 진행해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
let csvContent = "\uFEFF"; // BOM for Excel UTF-8
|
||||
const rows = table.querySelectorAll('tr');
|
||||
|
||||
rows.forEach(row => {
|
||||
const cols = row.querySelectorAll('th, td');
|
||||
const rowData = [];
|
||||
|
||||
cols.forEach(col => {
|
||||
// 따옴표 처리 및 텍스트 추출
|
||||
let text = col.innerText || col.textContent;
|
||||
text = text.replace(/"/g, '""'); // 따옴표 이스케이프
|
||||
|
||||
// 줄바꿈 제거
|
||||
text = text.replace(/(\r\n|\n|\r)/gm, " ");
|
||||
|
||||
if (text.includes(',') || text.includes('"')) {
|
||||
text = `"${text}"`;
|
||||
}
|
||||
rowData.push(text);
|
||||
});
|
||||
|
||||
csvContent += rowData.join(',') + "\r\n";
|
||||
});
|
||||
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const timestamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-');
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `server_config_comparison_${timestamp}.csv`);
|
||||
link.style.visibility = 'hidden';
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
Reference in New Issue
Block a user