386 lines
12 KiB
JavaScript
386 lines
12 KiB
JavaScript
/**
|
|
* Dell iDRAC 멀티 서버 관리 JavaScript
|
|
* backend/static/js/idrac_main.js
|
|
*/
|
|
|
|
// SocketIO 연결 (혹시 모를 추후 사용을 위해 유지하나, 현재는 직접적 이벤트 없음)
|
|
const socket = io();
|
|
|
|
// 전역 변수
|
|
let servers = [];
|
|
let selectedServers = new Set();
|
|
|
|
// ========================================
|
|
// 유틸리티 (CSRF, Fetch, Toast)
|
|
// ========================================
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
setTimeout(() => {
|
|
toast.classList.remove('show');
|
|
setTimeout(() => toast.remove(), 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// ========================================
|
|
// 초기화
|
|
// ========================================
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
console.log('iDRAC 멀티 서버 관리 시스템 시작');
|
|
refreshServers();
|
|
loadGroups();
|
|
});
|
|
|
|
// ========================================
|
|
// 서버 관리 (목록, 렌더링)
|
|
// ========================================
|
|
|
|
async function refreshServers() {
|
|
try {
|
|
const groupEl = document.getElementById('group-filter');
|
|
const group = groupEl ? groupEl.value : 'all';
|
|
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 (!tbody) return;
|
|
|
|
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.model || '-'}</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) {
|
|
if (!dateStr) return '-';
|
|
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() {
|
|
const el = document.getElementById('server-count');
|
|
if (el) el.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');
|
|
const selectAll = document.getElementById('select-all');
|
|
if (selectAll) selectAll.checked = all.length > 0 && all.length === checked.length;
|
|
}
|
|
|
|
function updateSelectedCount() {
|
|
const el = document.getElementById('selected-count');
|
|
if (el) el.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');
|
|
if (!select) return;
|
|
|
|
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();
|
|
}
|
|
|
|
// ========================================
|
|
// 서버 CRUD
|
|
// ========================================
|
|
|
|
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);
|
|
}
|
|
|
|
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 = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
function editServer(serverId) {
|
|
showMessage('서버 수정 기능은 현재 준비 중입니다.', 'info');
|
|
// 추후 구현: 모달에 데이터 로드 후 띄우기
|
|
}
|
|
|
|
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');
|
|
}
|
|
}
|
|
|
|
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');
|
|
} else {
|
|
showMessage(data.message, 'error');
|
|
}
|
|
refreshServers();
|
|
} catch (error) {
|
|
showMessage('연결 테스트 실패: ' + error, 'error');
|
|
}
|
|
}
|
|
|
|
async function rebootSelected() {
|
|
const serverIds = getSelectedServerIds();
|
|
|
|
if (serverIds.length === 0) {
|
|
showMessage('서버를 선택하세요', 'warning');
|
|
return;
|
|
}
|
|
|
|
if (!confirm(`선택한 ${serverIds.length}대 서버를 재부팅하시겠습니까?`)) {
|
|
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');
|
|
} else {
|
|
showMessage(data.message, 'error');
|
|
}
|
|
|
|
refreshServers();
|
|
} catch (error) {
|
|
showMessage('재부팅 실패: ' + error, 'error');
|
|
}
|
|
}
|