update
This commit is contained in:
@@ -27,7 +27,7 @@ header {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -48,7 +48,7 @@ header .subtitle {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ header .subtitle {
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -302,7 +302,7 @@ header .subtitle {
|
||||
max-width: 600px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
@@ -423,8 +423,15 @@ header .subtitle {
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-message {
|
||||
@@ -531,20 +538,20 @@ footer {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
|
||||
.header-actions {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.header-actions button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
.bulk-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
||||
.modal-content {
|
||||
width: 95%;
|
||||
}
|
||||
@@ -556,3 +563,217 @@ input[type="checkbox"] {
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
탭 메뉴 스타일
|
||||
======================================== */
|
||||
|
||||
.tab-menu {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
flex: 1;
|
||||
padding: 12px 24px;
|
||||
border: 2px solid #ddd;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.tab-button:hover:not(.active) {
|
||||
background: #f8f9fa;
|
||||
border-color: #667eea;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
animation: fadeIn 0.3s ease-in;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
토스트 알림 스타일
|
||||
======================================== */
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 15px 25px;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
|
||||
transform: translateX(400px);
|
||||
transition: transform 0.3s ease-out;
|
||||
z-index: 2000;
|
||||
max-width: 400px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.toast.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.toast-success {
|
||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||||
}
|
||||
|
||||
.toast-error {
|
||||
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
|
||||
}
|
||||
|
||||
.toast-warning {
|
||||
background: linear-gradient(135deg, #ffc107 0%, #ff9800 100%);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.toast-info {
|
||||
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
로딩 스피너
|
||||
======================================== */
|
||||
|
||||
.loading-spinner {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(102, 126, 234, 0.2);
|
||||
border-radius: 50%;
|
||||
border-top-color: #667eea;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#comparison-loading {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
#comparison-loading p {
|
||||
margin-top: 20px;
|
||||
color: #666;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
비교 결과 개선 스타일
|
||||
======================================== */
|
||||
|
||||
.comparison-grid {
|
||||
display: grid;
|
||||
gap: 30px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.server-comparison-section {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.server-comparison-section h3 {
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
.comparison-card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #667eea;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: 15px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.comparison-card:hover {
|
||||
transform: translateX(5px);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.comparison-card.outdated {
|
||||
border-left-color: #dc3545;
|
||||
background: linear-gradient(to right, #fff5f5 0%, white 100%);
|
||||
}
|
||||
|
||||
.comparison-card.latest {
|
||||
border-left-color: #28a745;
|
||||
background: linear-gradient(to right, #f0fff4 0%, white 100%);
|
||||
}
|
||||
|
||||
.comparison-card.unknown {
|
||||
border-left-color: #ffc107;
|
||||
background: linear-gradient(to right, #fffbf0 0%, white 100%);
|
||||
}
|
||||
|
||||
.comparison-card code {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
반응형 개선
|
||||
======================================== */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tab-menu {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.toast {
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.comparison-card {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user