/**
* OpenManager AI - ๋ฐ์ดํฐ ์ฒ๋ฆฌ๊ธฐ
* ์๋ฒ ๋ฐ์ดํฐ ์ฒ๋ฆฌ, ํ์ด์ง๋ค์ด์
, ํํฐ๋ง ๋ฐ UI ์
๋ฐ์ดํธ ๋ก์ง์ ๊ตฌํํฉ๋๋ค.
*/
import { AIProcessor } from '../utils/AIProcessor.js';
import { CONFIG } from '../utils/config.js';
import { generateDummyData } from '../utils/DummyDataGenerator.js';
class DataProcessor {
constructor() {
try {
// ๊ธฐ๋ณธ ๋ฐ์ดํฐ ์ด๊ธฐํ
this.serverData = window.serverData || [];
this.filteredData = [];
this.currentFilter = 'all';
this.currentSort = 'status-critical'; // ๊ธฐ๋ณธ ์ ๋ ฌ์ ์ํ ๊ธฐ์ค(์ฌ๊ฐ > ๊ฒฝ๊ณ > ์ ์)์ผ๋ก ๋ณ๊ฒฝ
this.searchQuery = '';
this.currentPage = 1;
this.itemsPerPage = 6; // ํ์ด์ง๋น ์๋ฒ ์
// AI ๋ฌธ์ ํ์ด์ง๋ค์ด์
this.currentProblemPage = 1;
this.problemsPerPage = 5; // ํ์ด์ง๋น, ์ฒ์์ ํ์๋ ๋ฌธ์ ์
// ์ด๊ธฐํ ๋ก๊น
console.log('DataProcessor ์ด๊ธฐํ ์์...');
// AIProcessor ์ธ์คํด์ค ์ด๊ธฐํ ๊ฐ์
if (window.aiProcessor) {
this.aiProcessor = window.aiProcessor;
console.log('๊ธฐ์กด AIProcessor ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํฉ๋๋ค.');
} else if (typeof AIProcessor === 'function') {
// AIProcessor ํด๋์ค๊ฐ ์กด์ฌํ๋ฉด ์ธ์คํด์ค ์์ฑ
try {
window.aiProcessor = new AIProcessor();
this.aiProcessor = window.aiProcessor;
console.log("AIProcessor ์ธ์คํด์ค๋ฅผ ์๋ก ์์ฑํ์ต๋๋ค.");
} catch (e) {
console.error("AIProcessor ์ธ์คํด์ค ์์ฑ ์ค ์ค๋ฅ ๋ฐ์:", e);
this.aiProcessor = null;
}
} else {
console.warn("AIProcessor ํด๋์ค๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ ์ํ ํ๋จ ๋ก์ง์ ์ฌ์ฉํฉ๋๋ค.");
this.aiProcessor = null;
}
// ์๋ฒ ์ํ ํ๊ฐ ์๊ณ๊ฐ - ํตํฉ ๊ด๋ฆฌ
this.thresholds = {
critical: {
cpu: 90,
memory: 90,
disk: 90
},
warning: {
cpu: 70,
memory: 70,
disk: 70
}
};
// UI ์์ ์ฐธ์กฐ - ์์ ํ๊ฒ ์ฐธ์กฐ๋ฅผ ์๋ํฉ๋๋ค
this.findUIElements();
// ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก
if (this.hasRequiredElements()) {
this.registerEventListeners();
console.log('์ด๋ฒคํธ ๋ฆฌ์ค๋๊ฐ ๋ฑ๋ก๋์์ต๋๋ค.');
} else {
console.warn('์ผ๋ถ UI ์์๋ฅผ ์ฐพ์ ์ ์์ด ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก์ด ์ ํ๋ฉ๋๋ค.');
}
// ์๋ ๋ฐ์ดํฐ ์
๋ฐ์ดํธ (1๋ถ ๊ฐ๊ฒฉ)
setInterval(() => this.refreshData(), 60 * 1000);
// ์ด๊ธฐ ๋ฐ์ดํฐ ๋ก๋
this.loadData();
// ์๋ฒ ์ํ ํ๋จ ํตํฉ ๋ก์ง์ ์ ์ญ ํจ์๋ก ๋ฑ๋ก
window.getServerStatus = (server) => this.getServerStatus(server);
console.log('DataProcessor ์ด๊ธฐํ ์๋ฃ.');
} catch (error) {
console.error('DataProcessor ์ด๊ธฐํ ์ค ์ฌ๊ฐํ ์ค๋ฅ ๋ฐ์:', error);
// ์ต์ํ์ ๊ธฐ๋ฅ ๋ณด์ฅ
this.serverData = window.serverData || [];
this.filteredData = window.serverData || [];
// ๊ธฐ๋ณธ ํ์ ํจ์๋ค์ ์ต์ํ์ผ๋ก๋ผ๋ ๊ตฌํ
if (!this.showLoading) {
this.showLoading = function() {
const loadingIndicator = document.getElementById('loadingIndicator');
const serverGrid = document.getElementById('serverGrid');
if (loadingIndicator) loadingIndicator.style.display = 'block';
if (serverGrid) serverGrid.style.opacity = '0.3';
};
}
if (!this.hideLoading) {
this.hideLoading = function() {
const loadingIndicator = document.getElementById('loadingIndicator');
const serverGrid = document.getElementById('serverGrid');
if (loadingIndicator) loadingIndicator.style.display = 'none';
if (serverGrid) serverGrid.style.opacity = '1';
};
}
// ๊ธฐ๋ณธ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ํจ์
if (!this.handleDataUpdate) {
this.handleDataUpdate = function(data) {
this.serverData = data || [];
this.filteredData = data || [];
this.renderServerGrid();
this.hideLoading();
};
}
}
}
// UI ์์๋ฅผ ์ฐพ์ ์ฐธ์กฐ๋ฅผ ์ ์ฅํ๋ ๋ฉ์๋
findUIElements() {
try {
// UI ์์ ์ฐธ์กฐ
this.serverGrid = document.getElementById('serverGrid');
this.loadingIndicator = document.getElementById('loadingIndicator');
this.searchInput = document.getElementById('searchInput');
this.statusFilter = document.getElementById('statusFilter');
this.pageSize = document.getElementById('pageSize');
this.prevPageBtn = document.getElementById('prevPageBtn');
this.nextPageBtn = document.getElementById('nextPageBtn');
this.serverCount = document.getElementById('serverCount');
this.currentPageElement = document.getElementById('currentPage');
this.refreshButton = document.getElementById('refreshBtn');
this.modalElement = document.getElementById('serverDetailModal');
this.closeModalButton = document.querySelector('.btn-close[data-bs-dismiss="modal"]');
// ํ์ด์ง๋ค์ด์
์ปจํ
์ด๋ ์์
this.pagination = document.querySelector('.pagination-container');
// ๋ชจ๋ ์์ ์ฐธ์กฐ๊ฐ ํ์ํ์ง ์๋์ง ํ์ธ ๋ก๊น
const missingElements = [];
if (!this.serverGrid) missingElements.push('serverGrid');
if (!this.loadingIndicator) missingElements.push('loadingIndicator');
if (!this.refreshButton) missingElements.push('refreshBtn');
if (missingElements.length > 0) {
console.warn(`๋ค์ UI ์์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค: ${missingElements.join(', ')}`);
}
} catch (error) {
console.error('UI ์์ ์ฐธ์กฐ ์ค ์ค๋ฅ:', error);
}
}
// ํ์ UI ์์๊ฐ ์๋์ง ํ์ธ
hasRequiredElements() {
// ์ต์ํ ์๋ฒ ๊ทธ๋ฆฌ๋์ ๋ก๋ฉ ์ธ๋์ผ์ดํฐ๋ ํ์
return this.serverGrid && this.loadingIndicator;
}
registerEventListeners() {
// ํํฐ๋ง ์ด๋ฒคํธ
if (this.statusFilter) {
this.statusFilter.addEventListener('change', () => {
this.currentFilter = this.statusFilter.value;
this.currentPage = 1; // ํํฐ ๋ณ๊ฒฝ ์ ์ฒซ ํ์ด์ง๋ก ๋ฆฌ์
this.applyFiltersAndSort();
});
}
// ๊ฒ์ ์ด๋ฒคํธ
if (this.searchInput) {
this.searchInput.addEventListener('input', () => {
this.searchQuery = this.searchInput.value.toLowerCase();
this.currentPage = 1; // ๊ฒ์ ์ ์ฒซ ํ์ด์ง๋ก ๋ฆฌ์
this.applyFiltersAndSort();
});
}
// ํ์ด์ง ํฌ๊ธฐ ์ด๋ฒคํธ
if (this.pageSize) {
this.pageSize.addEventListener('change', () => {
this.itemsPerPage = parseInt(this.pageSize.value);
this.currentPage = 1;
this.applyFiltersAndSort();
});
}
// ์ด์ ํ์ด์ง ๋ฒํผ
if (this.prevPageBtn) {
this.prevPageBtn.addEventListener('click', () => {
if (this.currentPage > 1) {
this.currentPage--;
this.applyFiltersAndSort();
}
});
}
// ๋ค์ ํ์ด์ง ๋ฒํผ
if (this.nextPageBtn) {
this.nextPageBtn.addEventListener('click', () => {
const totalPages = Math.ceil(this.filteredData.length / this.itemsPerPage);
if (this.currentPage < totalPages) {
this.currentPage++;
this.applyFiltersAndSort();
}
});
}
// ์๋ก๊ณ ์นจ ์ด๋ฒคํธ
if (this.refreshButton) {
this.refreshButton.addEventListener('click', () => this.refreshData());
}
// ๋ชจ๋ฌ ๋ซ๊ธฐ ์ด๋ฒคํธ
if (this.closeModalButton) {
this.closeModalButton.addEventListener('click', () => this.closeModal());
}
// ์๋ฒ ๋ฐ์ดํฐ ์
๋ฐ์ดํธ ์ด๋ฒคํธ ๋ฆฌ์ค๋
window.addEventListener('serverDataUpdated', (event) => {
this.handleDataUpdate(event.detail);
});
// ์ด๊ธฐ AI ๋์ฐ๋ฏธ ๊ธฐ๋ฅ ์ค์
const aiQuerySubmitButton = document.getElementById('ai-query-submit');
if (aiQuerySubmitButton) {
aiQuerySubmitButton.addEventListener('click', () => this.processAIQuery());
}
// ์ฅ์ ๋ณด๊ณ ์ ๋ค์ด๋ก๋ ์ด๋ฒคํธ
const downloadReportButton = document.getElementById('downloadAllReportsBtn');
if (downloadReportButton) {
downloadReportButton.addEventListener('click', () => this.downloadErrorReport());
}
// ์ ์ฒด ๋ฌธ์ ๋ณด๊ธฐ ๋ฒํผ ์ด๋ฒคํธ ์ฒ๋ฆฌ
const viewAllProblemsBtn = document.getElementById('viewAllProblemsBtn');
if (viewAllProblemsBtn) {
viewAllProblemsBtn.addEventListener('click', () => {
// ํ์ฌ ๋ชจ๋ ๋ฌธ์ ๋ฅผ ๋ชจ๋ฌ๋ก ํ์ํ๋๋ก ์์
this.showAllProblems();
});
}
// ํ๋ฆฌ์
ํ๊ทธ ๋ฒํผ ์ด๋ฒคํธ
document.querySelectorAll('.preset-tag').forEach(tag => {
tag.addEventListener('click', () => {
// ์ด์ ์ ์ ํ๋ ํ๊ทธ์ active ํด๋์ค ์ ๊ฑฐ
document.querySelectorAll('.preset-tag.active').forEach(el => {
el.classList.remove('active');
});
// ํด๋ฆญํ ํ๊ทธ์ active ํด๋์ค ์ถ๊ฐ
tag.classList.add('active');
const presetText = tag.dataset.preset;
const input = document.getElementById('queryInput');
if (input) {
input.value = presetText;
input.focus();
// ์๋ ์คํ
const submitButton = document.getElementById('ai-query-submit');
if (submitButton) {
submitButton.click();
}
}
// 0.5์ด ํ active ํด๋์ค ์ ๊ฑฐํ์ฌ ํด๋ฆญ ํจ๊ณผ ๋ฆฌ์
setTimeout(() => {
tag.classList.remove('active');
}, 500);
});
});
// ์ ์ฒด ๋ณด๊ณ ์ ๋ค์ด๋ก๋ ๋ฒํผ ์ด๋ฒคํธ ๋ฆฌ์ค๋
const downloadAllReportsBtn = document.getElementById('downloadAllReportsBtn');
if (downloadAllReportsBtn) {
downloadAllReportsBtn.addEventListener('click', () => {
this.downloadAllProblemsReport();
});
}
// ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ๋ซ๊ธฐ ๋ฒํผ ์ด๋ฒคํธ ๋ฆฌ์ค๋
const closeQueryResultBtn = document.getElementById('closeQueryResult');
if (closeQueryResultBtn) {
closeQueryResultBtn.addEventListener('click', () => {
const queryResultElement = document.getElementById('queryResult');
const queryResultContent = document.getElementById('queryResultContent');
if (queryResultElement) {
queryResultElement.style.display = 'none';
queryResultElement.classList.remove('active');
}
if (queryResultContent) {
queryResultContent.innerHTML = '';
}
// ์
๋ ฅ์ฐฝ๋ ๋น์ฐ๊ธฐ
const queryInput = document.getElementById('queryInput');
if (queryInput) {
queryInput.value = '';
}
console.log('AI ๋ถ์ ๊ฒฐ๊ณผ๊ฐ ๋ซํ์ต๋๋ค.');
});
}
}
loadData() {
this.showLoading();
// ๋ฐ์ดํฐ๊ฐ ์ด๋ฏธ ๋ก๋๋์ด ์์ผ๋ฉด ์ฌ์ฉ
if (window.serverData && window.serverData.length > 0) {
this.handleDataUpdate(window.serverData);
return;
}
// ๋ฐ์ดํฐ ๋ก๋ ์๋ (์ต๋ 10์ด ๋๊ธฐ)
let attempts = 0;
const maxAttempts = 20; // 10์ด = 500ms * 20
const checkInterval = setInterval(() => {
if (window.serverData && window.serverData.length > 0) {
clearInterval(checkInterval);
this.handleDataUpdate(window.serverData);
return;
}
attempts++;
if (attempts >= maxAttempts) {
clearInterval(checkInterval);
this.hideLoading();
console.error('์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ์ง ๋ชปํ์ต๋๋ค. ๋ฐฑ์
๋ฐ์ดํฐ ์์ฑ์ ์๋ํฉ๋๋ค.');
this.createBackupData();
}
}, 500);
}
// ๋ฐฑ์
๋ฐ์ดํฐ ์์ฑ ํจ์
createBackupData() {
console.log('๋ฐฑ์
๋ฐ์ดํฐ ์์ฑ ์๋...');
try {
// ๋๋ฏธ ๋ฐ์ดํฐ ์์ฑ ํจ์ ์ฌ์ฉ
window.serverData = generateDummyData(10); // 10๊ฐ์ ์๋ฒ ๋ฐ์ดํฐ ์์ฑ
if (window.serverData && window.serverData.length > 0) {
this.handleDataUpdate(window.serverData);
return;
}
// ๋ฐฑ์
์์ฑ ์คํจ ์ ๊ธฐ๋ณธ ๋๋ฏธ ๋ฐ์ดํฐ ์์ฑ
console.log('๊ธฐ๋ณธ ๋๋ฏธ ๋ฐ์ดํฐ ์์ฑ...');
const backupServers = [];
// ๊ธฐ๋ณธ ๋๋ฏธ ์๋ฒ 10๋ ์์ฑ
for (let i = 1; i <= 10; i++) {
// ์ฝ 30%์ ํ๋ฅ ๋ก ๋ฌธ์ ์๋ ์๋ฒ ์์ฑ
const hasProblem = Math.random() < 0.3;
const problemLevel = hasProblem ? (Math.random() < 0.3 ? 'critical' : 'warning') : 'normal';
const cpuUsage = problemLevel === 'critical' ?
Math.floor(Math.random() * 10) + 90 : // 90-99%
problemLevel === 'warning' ?
Math.floor(Math.random() * 20) + 70 : // 70-89%
Math.floor(Math.random() * 50) + 10; // 10-59%
const memoryUsage = problemLevel === 'critical' ?
Math.floor(Math.random() * 10) + 90 : // 90-99%
problemLevel === 'warning' ?
Math.floor(Math.random() * 20) + 70 : // 70-89%
Math.floor(Math.random() * 50) + 10; // 10-59%
const diskUsage = problemLevel === 'critical' ?
Math.floor(Math.random() * 10) + 90 : // 90-99%
problemLevel === 'warning' ?
Math.floor(Math.random() * 20) + 70 : // 70-89%
Math.floor(Math.random() * 50) + 20; // 20-69%
backupServers.push({
hostname: `server-${i}`,
os: 'Linux',
uptime: '3 days, 12:30:15',
cpu_usage: cpuUsage,
memory_usage_percent: memoryUsage,
memory_total: '16GB',
memory_used: '8GB',
disk: [{
mount: '/',
disk_total: '500GB',
disk_used: '300GB',
disk_usage_percent: diskUsage
}],
load_avg_1m: (Math.random() * 5).toFixed(2),
load_avg_5m: (Math.random() * 4).toFixed(2),
load_avg_15m: (Math.random() * 3).toFixed(2),
process_count: Math.floor(Math.random() * 200) + 50,
zombie_count: Math.floor(Math.random() * 3),
timestamp: new Date().toISOString(),
net: {
interface: 'eth0',
rx_bytes: Math.floor(Math.random() * 1000000),
tx_bytes: Math.floor(Math.random() * 1000000),
rx_errors: Math.floor(Math.random() * 10),
tx_errors: Math.floor(Math.random() * 10)
},
services: {
'nginx': problemLevel === 'critical' ? 'stopped' : 'running',
'mysql': Math.random() > 0.9 ? 'stopped' : 'running',
'redis': Math.random() > 0.9 ? 'stopped' : 'running'
},
errors: problemLevel !== 'normal' ?
[problemLevel === 'critical' ? 'Critical: ์๋ฒ ์๋ต ์์' : '๊ฒฝ๊ณ : ๋์ ๋ถํ ๊ฐ์ง'] : []
});
}
// ์ ์ญ ๋ณ์์ ์ ์ฅ ๋ฐ ์ด๋ฒคํธ ๋ฐ์
window.serverData = backupServers;
this.handleDataUpdate(backupServers);
// ์ด๋ฒคํธ ๋ฐ์์ํค๊ธฐ
const event = new CustomEvent('serverDataUpdated', {
detail: backupServers
});
window.dispatchEvent(event);
} catch (error) {
console.error('๋ฐฑ์
๋ฐ์ดํฐ ์์ฑ ์ค ์ค๋ฅ:', error);
// ์ต์ข
์คํจ ์ ์ค๋ฅ ๋ฉ์์ง ํ์
this.serverGrid.innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle"></i>
์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ์ง ๋ชปํ์ต๋๋ค. ์๋ก๊ณ ์นจ ๋ฒํผ์ ํด๋ฆญํ์ฌ ๋ค์ ์๋ํด ์ฃผ์ธ์.
</div>
`;
}
}
handleDataUpdate(data) {
this.serverData = [...data]; // ๋ฐ์ดํฐ ๋ณต์ฌ
this.hideLoading();
// ์ถ๊ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ (AI ๋ถ์ ๋ฑ)
if (this.aiProcessor) {
this.aiProcessor.updateData(this.serverData);
this.updateProblemsList(); // AI ์๋ ์ฅ์ ๋ณด๊ณ ์ ์
๋ฐ์ดํธ
}
// ํํฐ ๋ฐ ์ ๋ ฌ ์ ์ฉ
this.applyFiltersAndSort();
this.updateGlobalStatusSummary(); // ์๋ฒ ํํฉ ์์ฝ ์
๋ฐ์ดํธ ์ถ๊ฐ
this.updatePresetTagClasses(); // ํ๋ฆฌ์
ํ๊ทธ ํด๋์ค ์
๋ฐ์ดํธ
}
// ํ๋ฆฌ์
ํ๊ทธ ํด๋์ค ์
๋ฐ์ดํธ (์๋ฒ ์ํ์ ๋ฐ๋ผ ์์ ๋์ ๋ณ๊ฒฝ)
updatePresetTagClasses() {
// 1. CPU ๊ณผ๋ถํ ํ๋ฆฌ์
const cpuPresetTag = document.getElementById('cpu-preset');
if (cpuPresetTag) {
const highCpuServers = this.serverData.filter(server => server.cpu_usage >= this.thresholds.warning.cpu);
const criticalCpuServers = highCpuServers.filter(server => server.cpu_usage >= this.thresholds.critical.cpu);
// ํด๋์ค ์ด๊ธฐํ ๋ฐ ์ํ์ ๋ฐ๋ผ ์ค์
cpuPresetTag.classList.remove('tag-normal', 'tag-warning', 'tag-critical');
const badgeElement = cpuPresetTag.querySelector('.badge');
if (criticalCpuServers.length > 0) {
cpuPresetTag.classList.add('tag-critical');
if (badgeElement) {
badgeElement.className = 'badge bg-danger';
badgeElement.textContent = '์ฌ๊ฐ';
}
} else if (highCpuServers.length > 0) {
cpuPresetTag.classList.add('tag-warning');
if (badgeElement) {
badgeElement.className = 'badge bg-warning';
badgeElement.textContent = '๊ฒฝ๊ณ ';
}
} else {
cpuPresetTag.classList.add('tag-normal');
if (badgeElement) {
badgeElement.className = 'badge bg-success';
badgeElement.textContent = '์ ์';
}
}
}
// 2. ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ํ๋ฆฌ์
const memoryPresetTag = document.getElementById('memory-preset');
if (memoryPresetTag) {
const highMemoryServers = this.serverData.filter(server => server.memory_usage_percent >= this.thresholds.warning.memory);
const criticalMemoryServers = highMemoryServers.filter(server => server.memory_usage_percent >= this.thresholds.critical.memory);
// ํด๋์ค ์ด๊ธฐํ ๋ฐ ์ํ์ ๋ฐ๋ผ ์ค์
memoryPresetTag.classList.remove('tag-normal', 'tag-warning', 'tag-critical');
const badgeElement = memoryPresetTag.querySelector('.badge');
if (criticalMemoryServers.length > 0) {
memoryPresetTag.classList.add('tag-critical');
if (badgeElement) {
badgeElement.className = 'badge bg-danger';
badgeElement.textContent = '์ฌ๊ฐ';
}
} else if (highMemoryServers.length > 0) {
memoryPresetTag.classList.add('tag-warning');
if (badgeElement) {
badgeElement.className = 'badge bg-warning';
badgeElement.textContent = '๊ฒฝ๊ณ ';
}
} else {
memoryPresetTag.classList.add('tag-normal');
if (badgeElement) {
badgeElement.className = 'badge bg-success';
badgeElement.textContent = '์ ์';
}
}
}
// 3. ์๋น์ค ์ค๋จ ํ๋ฆฌ์
const servicePresetTag = document.getElementById('service-preset');
if (servicePresetTag) {
const stoppedServiceServers = this.serverData.filter(server =>
server.services && Object.values(server.services).some(status => status === 'stopped')
);
// ํด๋์ค ์ด๊ธฐํ ๋ฐ ์ํ์ ๋ฐ๋ผ ์ค์
servicePresetTag.classList.remove('tag-normal', 'tag-warning', 'tag-critical');
const badgeElement = servicePresetTag.querySelector('.badge');
if (stoppedServiceServers.length > 0) {
servicePresetTag.classList.add('tag-critical');
if (badgeElement) {
badgeElement.className = 'badge bg-danger';
badgeElement.textContent = '์ฌ๊ฐ';
}
} else {
servicePresetTag.classList.add('tag-normal');
if (badgeElement) {
badgeElement.className = 'badge bg-success';
badgeElement.textContent = '์ ์';
}
}
}
// 4. ๋์คํฌ ๋ถ์กฑ ํ๋ฆฌ์
const diskPresetTag = document.getElementById('disk-preset');
if (diskPresetTag) {
const highDiskServers = this.serverData.filter(server =>
server.disk &&
server.disk.length > 0 &&
server.disk[0].disk_usage_percent >= this.thresholds.warning.disk
);
const criticalDiskServers = highDiskServers.filter(server =>
server.disk[0].disk_usage_percent >= this.thresholds.critical.disk
);
// ํด๋์ค ์ด๊ธฐํ ๋ฐ ์ํ์ ๋ฐ๋ผ ์ค์
diskPresetTag.classList.remove('tag-normal', 'tag-warning', 'tag-critical');
const badgeElement = diskPresetTag.querySelector('.badge');
if (criticalDiskServers.length > 0) {
diskPresetTag.classList.add('tag-critical');
if (badgeElement) {
badgeElement.className = 'badge bg-danger';
badgeElement.textContent = '์ฌ๊ฐ';
}
} else if (highDiskServers.length > 0) {
diskPresetTag.classList.add('tag-warning');
if (badgeElement) {
badgeElement.className = 'badge bg-warning';
badgeElement.textContent = '๊ฒฝ๊ณ ';
}
} else {
diskPresetTag.classList.add('tag-normal');
if (badgeElement) {
badgeElement.className = 'badge bg-success';
badgeElement.textContent = '์ ์';
}
}
}
// 5. ์ ์ ์๋ฒ ํ๋ฆฌ์
(ํญ์ ์ ์ ํด๋์ค ์ ์ง, ์ ์ ๋ฐฐ์ง๋ง ํ์)
const normalPresetTag = document.getElementById('normal-preset');
if (normalPresetTag) {
const normalServers = this.serverData.filter(server => this.getServerStatus(server) === 'normal');
normalPresetTag.classList.remove('tag-warning', 'tag-critical');
normalPresetTag.classList.add('tag-normal');
// ๋ฐฐ์ง ์
๋ฐ์ดํธ
const badgeElement = normalPresetTag.querySelector('.badge');
if (badgeElement) {
badgeElement.className = 'badge bg-success';
badgeElement.textContent = `์ ์ (${normalServers.length})`;
}
}
}
refreshData() {
this.showLoading();
// ์๋ก๊ณ ์นจ ๋ฒํผ ์ํ ์
๋ฐ์ดํธ
const refreshBtn = document.getElementById('refreshBtn');
if (refreshBtn) {
refreshBtn.classList.add('loading');
refreshBtn.setAttribute('disabled', 'disabled');
const refreshContent = refreshBtn.querySelector('.refresh-content');
const loadingContent = refreshBtn.querySelector('.loading-content');
if (refreshContent) refreshContent.style.display = 'none';
if (loadingContent) loadingContent.style.display = 'inline-block';
}
// ํ์ด์ง ์ฌ๋ก๋๊ฐ ์๋ ๋ฐ์ดํฐ๋ง ์๋ก ์์ฑ
try {
console.log('๋๋ฏธ ๋ฐ์ดํฐ ๋ค์ ์์ฑ...');
window.serverData = generateDummyData(10); // 10๊ฐ์ ์๋ฒ ๋ฐ์ดํฐ ์์ฑ
// ๋ฐ์ดํฐ ์
๋ฐ์ดํธ ์ด๋ฒคํธ ๋ฐ์์ํค๊ธฐ
const event = new CustomEvent('serverDataUpdated', {
detail: window.serverData
});
window.dispatchEvent(event);
// 3์ด ํ ๋ก๋ฉ ์จ๊ธฐ๊ธฐ (์๋ก๊ณ ์นจ ํจ๊ณผ)
setTimeout(() => {
this.hideLoading();
// ์๋ก๊ณ ์นจ ๋ฒํผ ์ํ ๋ณต์
this.resetRefreshButton(refreshBtn);
}, 3000);
return;
} catch (e) {
console.error('๋ฐ์ดํฐ ์๋ก๊ณ ์นจ ์ค ์ค๋ฅ:', e);
// ์ค๋ฅ ๋ฐ์ ์ ์๋ ๋ฐ์ดํฐ๋ก UI ๋ณต์
this.hideLoading();
this.resetRefreshButton(refreshBtn);
// ์ค๋ฅ ๋ฉ์์ง ํ์
const errorAlert = document.createElement('div');
errorAlert.className = 'alert alert-danger alert-dismissible fade show';
errorAlert.innerHTML = `
<strong>์ค๋ฅ ๋ฐ์!</strong> ๋ฐ์ดํฐ๋ฅผ ์๋ก๊ณ ์นจํ๋ ์ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
// ์๋ฒ ๊ทธ๋ฆฌ๋ ์์ ์ค๋ฅ ๋ฉ์์ง ์ฝ์
const parent = this.serverGrid.parentNode;
parent.insertBefore(errorAlert, this.serverGrid);
// 5์ด ํ ์๋์ผ๋ก ์ค๋ฅ ๋ฉ์์ง ์ ๊ฑฐ
setTimeout(() => {
if (errorAlert.parentNode) {
errorAlert.parentNode.removeChild(errorAlert);
}
}, 5000);
return;
}
// ๋ฐ์ดํฐ๊ฐ ์
๋ฐ์ดํธ๋๋ฉด serverDataUpdated ์ด๋ฒคํธ๋ก ์ฒ๋ฆฌ๋จ
// ํ์ง๋ง 10์ด ๋ด์ ์
๋ฐ์ดํธ๊ฐ ์์ผ๋ฉด ํ์ฌ ๋ฐ์ดํฐ๋ก UI ๋ค์ ๋ก๋
setTimeout(() => {
if (this.loadingIndicator.style.display !== 'none') {
this.hideLoading();
this.applyFiltersAndSort();
// ์๋ก๊ณ ์นจ ๋ฒํผ ์ํ ๋ณต์
this.resetRefreshButton(refreshBtn);
}
}, 10000);
}
// ์๋ก๊ณ ์นจ ๋ฒํผ ์ํ ์ด๊ธฐํ ์ ํธ๋ฆฌํฐ ๋ฉ์๋
resetRefreshButton(refreshBtn) {
if (refreshBtn) {
refreshBtn.classList.remove('loading');
refreshBtn.removeAttribute('disabled');
const refreshContent = refreshBtn.querySelector('.refresh-content');
const loadingContent = refreshBtn.querySelector('.loading-content');
if (refreshContent) refreshContent.style.display = 'inline-block';
if (loadingContent) loadingContent.style.display = 'none';
}
}
showLoading() {
this.loadingIndicator.style.display = 'block';
this.serverGrid.style.opacity = '0.3';
}
hideLoading() {
this.loadingIndicator.style.display = 'none';
this.serverGrid.style.opacity = '1';
}
applyFiltersAndSort() {
try {
// ํํฐ ์ ์ฉ
this.filteredData = this.serverData.filter(server => {
// ๊ฒ์์ด ํํฐ
if (this.searchQuery && !server.hostname.toLowerCase().includes(this.searchQuery)) {
return false;
}
// ์ํ ํํฐ
if (this.currentFilter !== 'all') {
const status = this.getServerStatus(server);
return status === this.currentFilter;
}
return true;
});
// ์ ๋ ฌ ์ ์ฉ
this.sortData();
// UI ์
๋ฐ์ดํธ๋ฅผ ๋ณ๋ ๋ฉ์๋๋ก ๋ถ๋ฆฌํ์ฌ ์์ ํ๊ฒ ์คํ
this.updateUI();
} catch (error) {
console.error('ํํฐ ๋ฐ ์ ๋ ฌ ์ ์ฉ ์ค ์ค๋ฅ:', error);
// ์ค๋ฅ ๋ฐ์ ์ ๊ธฐ๋ณธ ์ฒ๋ฆฌ
this.filteredData = [...this.serverData];
// ์ต์ํ์ UI ์
๋ฐ์ดํธ ์๋
this.hideLoading();
this.safeUpdateServerGrid();
}
}
// ์์ ํ๊ฒ UI ์์๋ค์ ์
๋ฐ์ดํธํ๋ ๋ฉ์๋
updateUI() {
try {
// ์๋ฒ ๊ทธ๋ฆฌ๋ ์
๋ฐ์ดํธ
this.updateServerGrid();
} catch (e) {
console.error('์๋ฒ ๊ทธ๋ฆฌ๋ ์
๋ฐ์ดํธ ์ค ์ค๋ฅ:', e);
this.safeUpdateServerGrid();
}
try {
// ํ์ด์ง๋ค์ด์
์
๋ฐ์ดํธ
this.updateNumericPagination();
} catch (e) {
console.error('ํ์ด์ง๋ค์ด์
์
๋ฐ์ดํธ ์ค ์ค๋ฅ:', e);
}
try {
// ์๋ฒ ์นด์ดํธ ์
๋ฐ์ดํธ
this.updateServerCount();
} catch (e) {
console.error('์๋ฒ ์นด์ดํธ ์
๋ฐ์ดํธ ์ค ์ค๋ฅ:', e);
}
}
// ์ต์ํ์ ์์ ํ ์๋ฒ ๊ทธ๋ฆฌ๋ ์
๋ฐ์ดํธ
safeUpdateServerGrid() {
if (!this.serverGrid) return;
try {
this.serverGrid.innerHTML = '';
if (this.filteredData.length === 0) {
this.serverGrid.innerHTML = `
<div class="alert alert-info">
<i class="bi bi-info-circle"></i>
๊ฒ์ ์กฐ๊ฑด์ ๋ง๋ ์๋ฒ๊ฐ ์์ต๋๋ค.
</div>
`;
return;
}
// ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์๋ฒ ๋ชฉ๋ก๋ง ํ์
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = Math.min(startIndex + this.itemsPerPage, this.filteredData.length);
for (let i = startIndex; i < endIndex; i++) {
const server = this.filteredData[i];
if (!server) continue;
const div = document.createElement('div');
div.className = 'server-card';
div.textContent = server.hostname || 'Unknown Server';
this.serverGrid.appendChild(div);
}
} catch (e) {
console.error('์์ ํ ์๋ฒ ๊ทธ๋ฆฌ๋ ์
๋ฐ์ดํธ ์ค ์ค๋ฅ:', e);
this.serverGrid.innerHTML = '<div class="alert alert-danger">์๋ฒ ๋ฐ์ดํฐ ํ์ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.</div>';
}
}
sortData() {
switch(this.currentSort) {
case 'name':
this.filteredData.sort((a, b) => a.hostname.localeCompare(b.hostname));
break;
case 'cpu-high':
this.filteredData.sort((a, b) => b.cpu_usage - a.cpu_usage);
break;
case 'cpu-low':
this.filteredData.sort((a, b) => a.cpu_usage - b.cpu_usage);
break;
case 'memory-high':
this.filteredData.sort((a, b) => b.memory_usage_percent - a.memory_usage_percent);
break;
case 'memory-low':
this.filteredData.sort((a, b) => a.memory_usage_percent - b.memory_usage_percent);
break;
case 'disk-high':
this.filteredData.sort((a, b) => b.disk[0].disk_usage_percent - a.disk[0].disk_usage_percent);
break;
case 'disk-low':
this.filteredData.sort((a, b) => a.disk[0].disk_usage_percent - b.disk[0].disk_usage_percent);
break;
case 'status-critical':
this.filteredData.sort((a, b) => {
const statusA = this.getServerStatus(a);
const statusB = this.getServerStatus(b);
const statusWeight = { 'critical': 3, 'warning': 2, 'normal': 1 };
return statusWeight[statusB] - statusWeight[statusA];
});
break;
default:
// ๊ธฐ๋ณธ ์ ๋ ฌ: ์ฌ๊ฐ > ๊ฒฝ๊ณ > ์ ์ ์
this.filteredData.sort((a, b) => {
const statusA = this.getServerStatus(a);
const statusB = this.getServerStatus(b);
const statusWeight = { 'critical': 3, 'warning': 2, 'normal': 1 };
return statusWeight[statusB] - statusWeight[statusA];
});
}
}
updateServerGrid() {
if (!this.serverGrid) {
console.error("์๋ฒ ๊ทธ๋ฆฌ๋ ์์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.");
return;
}
this.serverGrid.innerHTML = '';
// ํ์ฌ ํ์ด์ง ์ฌ์ด์ฆ ์ค์ ์ฌ์ฉ (๋ ์ด์ ๊ณ ์ ๊ฐ ์ฌ์ฉ ์ ํจ)
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = Math.min(startIndex + this.itemsPerPage, this.filteredData.length);
if (this.filteredData.length === 0) {
this.serverGrid.innerHTML = `
<div class="alert alert-info">
<i class="bi bi-info-circle"></i>
๊ฒ์ ์กฐ๊ฑด์ ๋ง๋ ์๋ฒ๊ฐ ์์ต๋๋ค.
</div>
`;
return;
}
for (let i = startIndex; i < endIndex; i++) {
const server = this.filteredData[i];
const serverCard = this.createServerCard(server);
this.serverGrid.appendChild(serverCard);
}
// ํ์ด์ง๋ค์ด์
์
๋ฐ์ดํธ
this.updateNumericPagination();
}
updateNumericPagination() {
const paginationContainer = document.querySelector('.pagination-container');
if (!paginationContainer) return;
paginationContainer.innerHTML = '';
const totalPages = Math.ceil(this.filteredData.length / this.itemsPerPage);
// ์ด์ ํ์ด์ง ๋ฒํผ
const prevBtn = document.createElement('button');
prevBtn.className = 'btn btn-sm btn-outline-secondary';
prevBtn.innerHTML = '<i class="fas fa-chevron-left"></i>';
prevBtn.disabled = this.currentPage === 1;
prevBtn.addEventListener('click', () => {
if (this.currentPage > 1) {
this.currentPage--;
this.updateServerGrid();
}
});
paginationContainer.appendChild(prevBtn);
// ํ์ด์ง ๋ฒํธ ๋ฒํผ
const maxVisiblePages = 5;
let startPage = Math.max(1, this.currentPage - Math.floor(maxVisiblePages / 2));
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
// ํ์ํ ํ์ด์ง ๋ฒํผ์ด ์ต๋ ๊ฐ์๋ณด๋ค ์ ์ ๊ฒฝ์ฐ startPage ์กฐ์
if (endPage - startPage + 1 < maxVisiblePages && startPage > 1) {
startPage = Math.max(1, endPage - maxVisiblePages + 1);
}
// ์ฒซ ํ์ด์ง๋ก ์ด๋ ๋ฒํผ (ํ์ ์)
if (startPage > 1) {
const firstPageBtn = document.createElement('button');
firstPageBtn.className = 'btn btn-sm btn-outline-secondary mx-1';
firstPageBtn.textContent = '1';
firstPageBtn.addEventListener('click', () => {
this.currentPage = 1;
this.updateServerGrid();
});
paginationContainer.appendChild(firstPageBtn);
// ์๋ต ํ์ (์ฒซ ํ์ด์ง ๋ฒํผ๊ณผ ํ์ฌ ๋ฒ์ ์ฌ์ด)
if (startPage > 2) {
const ellipsis = document.createElement('span');
ellipsis.className = 'mx-1';
ellipsis.textContent = '...';
paginationContainer.appendChild(ellipsis);
}
}
// ํ์ด์ง ๋ฒํผ ์์ฑ
for (let i = startPage; i <= endPage; i++) {
const pageBtn = document.createElement('button');
pageBtn.className = `btn btn-sm mx-1 ${i === this.currentPage ? 'btn-primary' : 'btn-outline-secondary'}`;
pageBtn.textContent = i.toString();
pageBtn.addEventListener('click', () => {
this.currentPage = i;
this.updateServerGrid();
});
paginationContainer.appendChild(pageBtn);
}
// ๋ง์ง๋ง ํ์ด์ง๋ก ์ด๋ ๋ฒํผ (ํ์ ์)
if (endPage < totalPages) {
// ์๋ต ํ์ (ํ์ฌ ๋ฒ์์ ๋ง์ง๋ง ํ์ด์ง ๋ฒํผ ์ฌ์ด)
if (endPage < totalPages - 1) {
const ellipsis = document.createElement('span');
ellipsis.className = 'mx-1';
ellipsis.textContent = '...';
paginationContainer.appendChild(ellipsis);
}
const lastPageBtn = document.createElement('button');
lastPageBtn.className = 'btn btn-sm btn-outline-secondary mx-1';
lastPageBtn.textContent = totalPages.toString();
lastPageBtn.addEventListener('click', () => {
this.currentPage = totalPages;
this.updateServerGrid();
});
paginationContainer.appendChild(lastPageBtn);
}
// ๋ค์ ํ์ด์ง ๋ฒํผ
const nextBtn = document.createElement('button');
nextBtn.className = 'btn btn-sm btn-outline-secondary';
nextBtn.innerHTML = '<i class="fas fa-chevron-right"></i>';
nextBtn.disabled = this.currentPage === totalPages;
nextBtn.addEventListener('click', () => {
if (this.currentPage < totalPages) {
this.currentPage++;
this.updateServerGrid();
}
});
paginationContainer.appendChild(nextBtn);
// ํ์ฌ ํ์ด์ง ์ ๋ณด ์
๋ฐ์ดํธ
if (this.currentPageElement) {
this.currentPageElement.style.display = 'none'; // ๊ธฐ์กด ํ์ด์ง ํ์ ์จ๊น
}
}
createServerCard(server) {
if (!server) {
console.error("์๋ฒ ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.");
return document.createElement('div'); // ๋น ์์ ๋ฐํ
}
const status = this.getServerStatus(server);
// ์์ ํ๊ฒ services ์ฒดํฌ
const hasStoppedServices = server.services && Object.values(server.services).some(status => status === 'stopped');
// ์์ ํ๊ฒ errors ์ฒดํฌ
const hasErrors = server.errors && Array.isArray(server.errors) && server.errors.length > 0;
// ์๋ฒ ์นด๋ ์์ฑ
const serverCard = document.createElement('div');
serverCard.className = 'server-card status-' + status;
serverCard.dataset.serverId = server.hostname || 'unknown';
// ํดํ ์์ ์ถ๊ฐ
const tooltip = document.createElement('div');
tooltip.className = 'tooltip-wrapper';
tooltip.innerHTML = `
<div class="tooltip-content">
ํด๋ฆญํ์ฌ ${server.hostname} ์๋ฒ์ ์์ธ ์ ๋ณด๋ฅผ ํ์ธํ์ธ์.
</div>
`;
serverCard.appendChild(tooltip);
// ์นด๋ ํด๋ฆญ ์ ์์ธ ์ ๋ณด ๋ชจ๋ฌ ํ์
serverCard.addEventListener('click', () => this.showServerDetail(server));
// CPU, ๋ฉ๋ชจ๋ฆฌ, ๋์คํฌ ์ฌ์ฉ๋ฅ ์ ์์ ํ๊ฒ ๊ฐ์ ธ์ค๊ธฐ
const cpuUsage = server.cpu_usage || 0;
const memoryUsage = server.memory_usage_percent || 0;
const diskUsage = server.disk && server.disk[0] ? server.disk[0].disk_usage_percent || 0 : 0;
const diskMount = server.disk && server.disk[0] ? server.disk[0].mount || '/' : '/';
// ๋ฆฌ์์ค ์ํ ํ๋ณ
const cpuStatus = this.getResourceStatus(cpuUsage, 'cpu');
const memoryStatus = this.getResourceStatus(memoryUsage, 'memory');
const diskStatus = this.getResourceStatus(diskUsage, 'disk');
// ์ํ๋ณ ์์ ํด๋์ค
const getStatusColorClass = (status) => {
switch(status) {
case 'critical': return 'text-danger';
case 'warning': return 'text-warning';
default: return 'text-success';
}
};
// ์ํ ์์ด์ฝ ๊ฐ์ ธ์ค๊ธฐ
const getStatusIcon = (status) => {
switch(status) {
case 'critical': return '<i class="fas fa-exclamation-circle me-1"></i>';
case 'warning': return '<i class="fas fa-exclamation-triangle me-1"></i>';
default: return '<i class="fas fa-check-circle me-1"></i>';
}
};
// ์นด๋ ๋ด์ฉ ๊ตฌ์ฑ
serverCard.innerHTML = `
<div class="card-interaction-hint">
<i class="fas fa-info-circle"></i>
</div>
<div class="server-header">
<div class="server-name">${server.hostname || 'Unknown Server'}</div>
<div class="server-status status-${status}">${this.getStatusLabel(status)}</div>
</div>
<div class="server-details">
<div class="detail-item">
<div class="detail-label">CPU ์ฌ์ฉ๋</div>
<div class="detail-value ${getStatusColorClass(cpuStatus)}">
${getStatusIcon(cpuStatus)}<strong>${cpuUsage}%</strong>
</div>
<div class="progress-bar-container">
<div class="progress-bar progress-${cpuStatus}"
style="width: ${cpuUsage}%"></div>
</div>
</div>
<div class="detail-item">
<div class="detail-label">๋ฉ๋ชจ๋ฆฌ</div>
<div class="detail-value ${getStatusColorClass(memoryStatus)}">
${getStatusIcon(memoryStatus)}<strong>${typeof memoryUsage === 'number' ? memoryUsage.toFixed(1) : '0'}%</strong>
</div>
<div class="progress-bar-container">
<div class="progress-bar progress-${memoryStatus}"
style="width: ${memoryUsage}%"></div>
</div>
</div>
<div class="detail-item">
<div class="detail-label">๋์คํฌ (${diskMount})</div>
<div class="detail-value ${getStatusColorClass(diskStatus)}">
${getStatusIcon(diskStatus)}<strong>${typeof diskUsage === 'number' ? diskUsage.toFixed(1) : '0'}%</strong>
</div>
<div class="progress-bar-container">
<div class="progress-bar progress-${diskStatus}"
style="width: ${diskUsage}%"></div>
</div>
</div>
<div class="detail-item">
<div class="detail-label">๋ก๋ ํ๊ท </div>
<div class="detail-value">${server.load_avg_1m || '0'}</div>
</div>
</div>
<div class="services-list">
${server.services ? Object.entries(server.services).map(([name, status]) => `
<div class="service-badge service-${status}">${name} (${status})</div>
`).join('') : '<div class="service-badge">์๋น์ค ์ ๋ณด ์์</div>'}
</div>
${hasErrors ? `
<div class="error-messages">
<i class="bi bi-exclamation-triangle-fill"></i> ${server.errors.length}๊ฐ์ ์ค๋ฅ
</div>
` : ''}
`;
return serverCard;
}
showServerDetail(server) {
if (!server) {
console.error("์๋ฒ ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.");
return;
}
try {
// ๋ถํธ์คํธ๋ฉ ๋ชจ๋ฌ ์์ ๋ฐ ๋ด์ฉ์ ์ฐพ๊ธฐ
const modalElement = document.getElementById('serverDetailModal');
if (!modalElement) {
console.error("๋ชจ๋ฌ ์์(serverDetailModal)๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.");
return;
}
// ๋ชจ๋ฌ ํค๋ ๋ด์์ ์ ๋ชฉ ์์ ์ฐพ๊ธฐ
const modalTitle = modalElement.querySelector('.modal-title');
if (!modalTitle) {
console.error("๋ชจ๋ฌ ์ ๋ชฉ ์์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.");
return;
}
// ๋ชจ๋ฌ ๋ด์ฉ์ ํ์ํ ์์ ์ฐพ๊ธฐ
const modalBody = modalElement.querySelector('.modal-body');
if (!modalBody) {
console.error("๋ชจ๋ฌ ๋ด์ฉ ์์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.");
return;
}
// ์๋ฒ ์ํ ์ ๋ณด
const status = this.getServerStatus(server);
// ์๋ฒ ์ด๋ฆ๊ณผ ์ํ ์ค์
modalTitle.innerHTML = `
${server.hostname}
<span class="server-status status-${status}">${this.getStatusLabel(status)}</span>
`;
// ๋ชจ๋ฌ ๋ด์ฉ ์
๋ฐ์ดํธ - ๊ฐ๋ณ ํ๋ ์
๋ฐ์ดํธ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝ
// OS ์ ๋ณด
const modalOS = document.getElementById('modalOS');
if (modalOS) modalOS.textContent = server.os || '-';
// ๊ฐ๋ ์๊ฐ
const modalUptime = document.getElementById('modalUptime');
if (modalUptime) modalUptime.textContent = server.uptime || '-';
// ํ๋ก์ธ์ค ์
const modalProcessCount = document.getElementById('modalProcessCount');
if (modalProcessCount) modalProcessCount.textContent = server.process_count || '-';
// ์ข๋น ํ๋ก์ธ์ค
const modalZombieCount = document.getElementById('modalZombieCount');
if (modalZombieCount) modalZombieCount.textContent = server.zombie_count || '-';
// ๋ก๋ ํ๊ท
const modalLoadAvg = document.getElementById('modalLoadAvg');
if (modalLoadAvg) modalLoadAvg.textContent = server.load_avg_1m || '-';
// ๋ง์ง๋ง ์
๋ฐ์ดํธ
const modalLastUpdate = document.getElementById('modalLastUpdate');
if (modalLastUpdate) modalLastUpdate.textContent = new Date(server.timestamp).toLocaleString() || '-';
// ๋คํธ์ํฌ ์ ๋ณด
const modalNetInterface = document.getElementById('modalNetInterface');
if (modalNetInterface) modalNetInterface.textContent = server.net?.interface || '-';
const modalRxBytes = document.getElementById('modalRxBytes');
if (modalRxBytes) modalRxBytes.textContent = this.formatBytes(server.net?.rx_bytes) || '-';
const modalTxBytes = document.getElementById('modalTxBytes');
if (modalTxBytes) modalTxBytes.textContent = this.formatBytes(server.net?.tx_bytes) || '-';
const modalRxErrors = document.getElementById('modalRxErrors');
if (modalRxErrors) modalRxErrors.textContent = server.net?.rx_errors || '-';
const modalTxErrors = document.getElementById('modalTxErrors');
if (modalTxErrors) modalTxErrors.textContent = server.net?.tx_errors || '-';
// ์๋น์ค ์ํ
const modalServiceStatus = document.getElementById('modalServiceStatus');
if (modalServiceStatus) {
modalServiceStatus.innerHTML = '';
if (server.services && Object.keys(server.services).length > 0) {
Object.entries(server.services).forEach(([name, status]) => {
const serviceTag = document.createElement('div');
serviceTag.className = `service-status-tag service-${status}`;
serviceTag.innerHTML = `
${name}
<span class="status-indicator">
<i class="fas fa-${status === 'running' ? 'check-circle' : 'times-circle'}"></i>
</span>
`;
modalServiceStatus.appendChild(serviceTag);
});
} else {
modalServiceStatus.innerHTML = '<div class="alert alert-info">์๋น์ค ์ ๋ณด๊ฐ ์์ต๋๋ค.</div>';
}
}
// ์ค๋ฅ ๋ฉ์์ง
const modalErrorsContainer = document.getElementById('modalErrorsContainer');
if (modalErrorsContainer) {
if (server.errors && server.errors.length > 0) {
modalErrorsContainer.innerHTML = `
<div class="alert alert-danger">
<ul class="mb-0">
${server.errors.map(error => `<li>${error}</li>`).join('')}
</ul>
</div>
`;
} else {
modalErrorsContainer.innerHTML = '<div class="alert alert-info">ํ์ฌ ๋ณด๊ณ ๋ ์ค๋ฅ๊ฐ ์์ต๋๋ค.</div>';
}
}
// jQuery๋ก ๋ชจ๋ฌ ํ์ ์๋ (๋ถํธ์คํธ๋ฉ ์์กด์ฑ ์ค์ด๊ธฐ)
if (window.jQuery && window.jQuery.fn.modal) {
window.jQuery(modalElement).modal('show');
// ๋ฆฌ์์ค ์ฐจํธ ์์ฑ
this.createResourceChart(server);
// ์๋ฒ ์ด๋ฆ์ ์๋ฒ ์ด๋ฆ ์์์ ์ค์
const modalServerName = document.getElementById('modalServerName');
if (modalServerName) modalServerName.textContent = `${server.hostname} ์์ธ ์ ๋ณด`;
return; // jQuery๋ก ์ฑ๊ณต์ ์ผ๋ก ํ์ํ์ผ๋ฉด ์ฌ๊ธฐ์ ์ข
๋ฃ
}
// ๋ถํธ์คํธ๋ฉ ๋ชจ๋ฌ ์ธ์คํด์ค ์์ฑ ๋ฐ ํ์
if (typeof bootstrap !== 'undefined' && typeof bootstrap.Modal === 'function') {
// ๋ถํธ์คํธ๋ฉ 5+
const bsModal = new bootstrap.Modal(modalElement);
bsModal.show();
} else {
// ์์ JavaScript๋ก ๋ชจ๋ฌ ํ์ (fallback)
modalElement.style.display = 'block';
modalElement.classList.add('show');
document.body.classList.add('modal-open');
// ๋ฐฐ๊ฒฝ ์์ ์ถ๊ฐ
const backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop fade show';
document.body.appendChild(backdrop);
// ๋ซ๊ธฐ ๋ฒํผ์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ถ๊ฐ
const closeButtons = modalElement.querySelectorAll('[data-bs-dismiss="modal"]');
closeButtons.forEach(btn => {
btn.addEventListener('click', () => {
modalElement.style.display = 'none';
modalElement.classList.remove('show');
document.body.classList.remove('modal-open');
document.querySelector('.modal-backdrop')?.remove();
});
});
}
// ๋ฆฌ์์ค ์ฐจํธ ์์ฑ
this.createResourceChart(server);
// ์๋ฒ ์ด๋ฆ์ ์๋ฒ ์ด๋ฆ ์์์ ์ค์
const modalServerName = document.getElementById('modalServerName');
if (modalServerName) modalServerName.textContent = `${server.hostname} ์์ธ ์ ๋ณด`;
} catch (error) {
console.error('์๋ฒ ์์ธ ์ ๋ณด ํ์ ์ค ์ค๋ฅ ๋ฐ์:', error);
alert('์๋ฒ ์์ธ ์ ๋ณด๋ฅผ ํ์ํ ์ ์์ต๋๋ค.');
}
}
closeModal() {
this.modalElement.style.display = 'none';
}
createResourceChart(server) {
const chartElement = document.getElementById('resourceBarChart');
if (!chartElement) {
console.error("๋ฆฌ์์ค ์ฐจํธ ์์(resourceBarChart)๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.");
return;
}
const ctx = chartElement.getContext('2d');
if (!ctx) {
console.error("๋ฆฌ์์ค ์ฐจํธ ์ปจํ
์คํธ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.");
return;
}
// ๊ธฐ์กด ์ฐจํธ๊ฐ ์๋ค๋ฉด ํ๊ดด
if (this.resourceChartInstance) {
this.resourceChartInstance.destroy();
}
this.resourceChartInstance = new Chart(ctx, {
type: 'bar',
data: {
labels: ['CPU', '๋ฉ๋ชจ๋ฆฌ', '๋์คํฌ'],
datasets: [{
label: '์ฌ์ฉ๋ (%)',
data: [
server.cpu_usage || 0,
server.memory_usage_percent || 0,
(server.disk && server.disk.length > 0) ? server.disk[0].disk_usage_percent || 0 : 0
],
backgroundColor: [
this.getChartColor(server.cpu_usage || 0),
this.getChartColor(server.memory_usage_percent || 0),
this.getChartColor((server.disk && server.disk.length > 0) ? server.disk[0].disk_usage_percent || 0 : 0)
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100
}
}
}
});
}
createHistoryChart(hostname) {
const historicalData = window.serverHistoricalData[hostname];
if (!historicalData || historicalData.length === 0) return;
// history-chart-modal ID๋ฅผ ์ฌ์ฉํ๋๋ก ์์
const canvasElement = document.getElementById('history-chart-modal');
if (!canvasElement) {
console.error("History chart canvas element not found in modal");
return;
}
const ctx = canvasElement.getContext('2d');
if (!ctx) {
console.error("ํ์คํ ๋ฆฌ ์ฐจํธ ์ปจํ
์คํธ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.");
return;
}
// ๊ธฐ์กด ์ฐจํธ๊ฐ ์๋ค๋ฉด ํ๊ดด
if (this.historyChartInstance) {
this.historyChartInstance.destroy();
}
// ์๊ฐ ๋ ์ด๋ธ ์์ฑ (์ต๊ทผ 24์๊ฐ)
const labels = historicalData.map(data => {
const date = new Date(data.timestamp);
return `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
});
// CPU, ๋ฉ๋ชจ๋ฆฌ, ๋์คํฌ ๋ฐ์ดํฐ ์ถ์ถ
const cpuData = historicalData.map(data => data.cpu_usage);
const memoryData = historicalData.map(data => data.memory_usage_percent);
const diskData = historicalData.map(data => data.disk[0].disk_usage_percent);
this.historyChartInstance = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'CPU',
data: cpuData,
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderWidth: 2,
tension: 0.1
},
{
label: '๋ฉ๋ชจ๋ฆฌ',
data: memoryData,
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderWidth: 2,
tension: 0.1
},
{
label: '๋์คํฌ',
data: diskData,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderWidth: 2,
tension: 0.1
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100,
title: {
display: true,
text: '์ฌ์ฉ๋ (%)'
}
},
x: {
title: {
display: true,
text: '์๊ฐ'
}
}
}
}
});
}
updateProblemsList() {
if (!this.aiProcessor) return;
const problemListContainer = document.getElementById('aiProblemList');
const loadingIndicator = document.getElementById('aiProblemsLoading');
const emptyIndicator = document.getElementById('aiProblemsEmpty');
const paginationContainer = document.querySelector('.problem-pagination');
if (!problemListContainer || !loadingIndicator || !emptyIndicator) {
console.error("AI Problem list UI elements not found.");
return;
}
loadingIndicator.style.display = 'block';
emptyIndicator.style.display = 'none';
problemListContainer.innerHTML = '';
if (paginationContainer) paginationContainer.innerHTML = '';
try {
// detectProblems ๋ฉ์๋๊ฐ ์กด์ฌํ๊ณ ํธ์ถ ๊ฐ๋ฅํ์ง ํ์ธ
let problems = [];
if (typeof this.aiProcessor.detectProblems === 'function') {
problems = this.aiProcessor.detectProblems();
} else {
console.warn("AI ํ๋ก์ธ์์ detectProblems ๋ฉ์๋๊ฐ ์์ต๋๋ค. ๊ธฐ๋ณธ ๋ฌธ์ ๊ฐ์ง ๋ก์ง์ ์ฌ์ฉํฉ๋๋ค.");
// ๊ธฐ๋ณธ ๋ฌธ์ ๊ฐ์ง ๋ก์ง: ๋ฆฌ์์ค ์ฌ์ฉ๋์ด ๋์ ์๋ฒ ๊ฐ์ง
problems = this.serverData.filter(server => {
const status = this.getServerStatus(server);
if (status === 'critical') {
return {
severity: 'Critical',
serverHostname: server.hostname,
description: `๋ฆฌ์์ค ๊ณผ๋ถํ ๊ฐ์ง`,
solution: '์๋ฒ ์์ ํ์ธ ๋ฐ ๋ถํ์ํ ํ๋ก์ธ์ค๋ฅผ ์ข
๋ฃํ์ธ์.'
};
} else if (status === 'warning') {
return {
severity: 'Warning',
serverHostname: server.hostname,
description: `์์ ์ฌ์ฉ๋ ๋์`,
solution: '์๋ฒ ์ํ๋ฅผ ๋ชจ๋ํฐ๋งํ๊ณ ์ถ์ธ๋ฅผ ํ์ธํ์ธ์.'
};
}
return null;
}).filter(p => p !== null);
}
// ๊ฒฐ๊ณผ๊ฐ ๋ฐฐ์ด์ด ์๋๊ฑฐ๋ undefined์ธ ๊ฒฝ์ฐ ๋น ๋ฐฐ์ด๋ก ์ฒ๋ฆฌ
if (!Array.isArray(problems)) {
console.warn("detectProblems() ํจ์๊ฐ ๋ฐฐ์ด์ ๋ฐํํ์ง ์์์ต๋๋ค.");
problems = [];
}
// Normal ์ํ๋ ์ ์ธ (detectProblems์์ ์ด๋ฏธ ์ฒ๋ฆฌ๋์๊ฑฐ๋, ์ฌ๊ธฐ์ ํ๋ฒ ๋ ํํฐ๋ง)
problems = problems.filter(p => p && (p.severity === 'Critical' || p.severity === 'Warning' || p.severity === 'Error'));
// ์ ๋ ฌ: Critical ์ฐ์ , ๊ทธ ๋ค์ Warning(Error ํฌํจ) (๋ด๋ฆผ์ฐจ์)
problems.sort((a, b) => {
const severityScore = (severity) => {
if (severity === 'Critical') return 2;
if (severity === 'Warning' || severity === 'Error') return 1;
return 0; // ๊ทธ ์ธ (์ ์ ๋ฑ, ์ค์ ๋ก๋ ํํฐ๋ง๋จ)
};
return severityScore(b.severity) - severityScore(a.severity);
});
loadingIndicator.style.display = 'none';
if (problems.length === 0) {
emptyIndicator.style.display = 'block';
return;
}
// ์ ์ฒด ๋ฌธ์ ๋ฐ์ดํฐ ์ ์ฅ
this.problemsData = problems;
// ํ์ด์ง๋ค์ด์
๊ณ์ฐ
const totalProblems = problems.length;
const totalPages = Math.ceil(totalProblems / this.problemsPerPage);
// ํ์ฌ ํ์ด์ง๊ฐ ๋ฒ์๋ฅผ ๋ฒ์ด๋๋ฉด ์กฐ์
if (this.currentProblemPage > totalPages) {
this.currentProblemPage = totalPages;
}
// ํ์ฌ ํ์ด์ง์ ํ์ํ ๋ฌธ์ ๊ณ์ฐ
const startIdx = (this.currentProblemPage - 1) * this.problemsPerPage;
const endIdx = Math.min(startIdx + this.problemsPerPage, totalProblems);
const currentPageProblems = problems.slice(startIdx, endIdx);
// ๋ฌธ์ ํญ๋ชฉ ๋ ๋๋ง
currentPageProblems.forEach((problem, index) => {
const listItem = document.createElement('li');
listItem.className = `list-group-item list-group-item-action problem-item severity-${problem.severity.toLowerCase()}`;
// ์ธ๋ฑ์ค ๋ณ์ ์ถ๊ฐ
listItem.style.setProperty('--item-index', index);
listItem.innerHTML = `
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1 problem-description">${problem.description}</h6>
<small class="text-muted">${problem.serverHostname || '์ ์ ์๋ ์๋ฒ'}</small>
</div>
<p class="mb-1 problem-solution">${problem.solution || '์ ์๋ ํด๊ฒฐ์ฑ
์์'}</p>
<small class="text-muted">์ฌ๊ฐ๋: <span class="fw-bold problem-severity-text">${problem.severity}</span></small>
<div class="problem-hint-icon">
<i class="fas fa-search-plus"></i>
</div>
`;
// ๋ฌธ์ ํญ๋ชฉ ํด๋ฆญ ์ ์ก์
(์๋ฒ ์์ธ ๋ชจ๋ฌ)
listItem.addEventListener('click', () => {
// ์์ธ ๋ณด๊ณ ์ ๋ชจ๋ฌ ํ์
this.showProblemDetailModal(problem);
});
problemListContainer.appendChild(listItem);
});
// ํ์ด์ง๋ค์ด์
์์ฑ
if (paginationContainer && totalPages > 1) {
// ์ด์ ํ์ด์ง ๋ฒํผ
const prevBtn = document.createElement('button');
prevBtn.className = 'btn btn-sm btn-outline-secondary';
prevBtn.innerHTML = '<i class="fas fa-chevron-left"></i>';
prevBtn.disabled = this.currentProblemPage === 1;
prevBtn.addEventListener('click', () => {
if (this.currentProblemPage > 1) {
this.currentProblemPage--;
this.updateProblemsList();
}
});
paginationContainer.appendChild(prevBtn);
// ํ์ด์ง ๋ฒํธ ๋ฒํผ
const maxVisiblePages = 5;
let startPage = Math.max(1, this.currentProblemPage - Math.floor(maxVisiblePages / 2));
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
// ํ์ํ ํ์ด์ง ๋ฒํผ ์กฐ์
if (endPage - startPage + 1 < maxVisiblePages && startPage > 1) {
startPage = Math.max(1, endPage - maxVisiblePages + 1);
}
// ํ์ด์ง ๋ฒํผ ์์ฑ
for (let i = startPage; i <= endPage; i++) {
const pageBtn = document.createElement('button');
pageBtn.className = `btn btn-sm mx-1 ${i === this.currentProblemPage ? 'btn-primary' : 'btn-outline-secondary'}`;
pageBtn.textContent = i.toString();
pageBtn.addEventListener('click', () => {
this.currentProblemPage = i;
this.updateProblemsList();
});
paginationContainer.appendChild(pageBtn);
}
// ๋ค์ ํ์ด์ง ๋ฒํผ
const nextBtn = document.createElement('button');
nextBtn.className = 'btn btn-sm btn-outline-secondary';
nextBtn.innerHTML = '<i class="fas fa-chevron-right"></i>';
nextBtn.disabled = this.currentProblemPage === totalPages;
nextBtn.addEventListener('click', () => {
if (this.currentProblemPage < totalPages) {
this.currentProblemPage++;
this.updateProblemsList();
}
});
paginationContainer.appendChild(nextBtn);
}
} catch (error) {
console.error("AI ๋ฌธ์ ๋ชฉ๋ก ์
๋ฐ์ดํธ ์ค ์ค๋ฅ ๋ฐ์:", error);
loadingIndicator.style.display = 'none';
emptyIndicator.textContent = "๋ฌธ์ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.";
emptyIndicator.style.display = 'block';
}
}
// ์๋ฒ ์ ํ์ ์
๋ฐ์ดํธํ๋ ๋ฉ์๋
updateServerCount() {
if (!this.serverCount) return;
try {
const endIndex = Math.min(this.currentPage * this.itemsPerPage, this.filteredData.length);
const startIndex = this.filteredData.length > 0 ? (this.currentPage - 1) * this.itemsPerPage + 1 : 0;
if (this.filteredData.length > 0) {
this.serverCount.textContent = `์ ์ฒด ${this.serverData.length} ์๋ฒ ์ค ${startIndex}-${endIndex} ํ์ ์ค`;
} else {
this.serverCount.textContent = `์ ์ฒด ${this.serverData.length} ์๋ฒ ์ค 0 ํ์ ์ค`;
}
} catch (e) {
console.error("์๋ฒ ์นด์ดํธ ์
๋ฐ์ดํธ ์ค ์ค๋ฅ:", e);
}
}
updateGlobalStatusSummary() {
if (!this.serverData || this.serverData.length === 0) return;
const summaryContainer = document.getElementById('statusSummaryContainer');
if (!summaryContainer) {
console.error("Status summary container not found.");
return;
}
let normalCount = 0;
let warningCount = 0;
let criticalCount = 0;
this.serverData.forEach(server => {
const status = this.getServerStatus(server); // ์ค์ ์ง์ค์ ์ํ ํ๋จ ํจ์ ์ฌ์ฉ
if (status === 'normal') normalCount++;
else if (status === 'warning') warningCount++;
else if (status === 'critical') criticalCount++;
});
// ํ์์คํฌํ ์
๋ฐ์ดํธ
const timestampElement = document.getElementById('timestamp');
if (timestampElement) {
const latestTimestamp = this.serverData.reduce((latest, server) => {
const serverTime = new Date(server.timestamp).getTime();
return serverTime > latest ? serverTime : latest;
}, 0);
if (latestTimestamp > 0) {
timestampElement.textContent = `๋ฐ์ดํฐ ๊ธฐ์ค ์๊ฐ: ${new Date(latestTimestamp).toLocaleString()}`;
} else {
timestampElement.textContent = `๋ฐ์ดํฐ ๊ธฐ์ค ์๊ฐ: ${new Date().toLocaleString()}`;
}
}
summaryContainer.innerHTML = `
<div class="row mb-3">
<div class="col-4 text-center">
<h3 class="mb-0 display-6 text-success">${normalCount}</h3>
<p class="text-success mb-0">์ ์</p>
</div>
<div class="col-4 text-center">
<h3 class="mb-0 display-6 text-warning">${warningCount}</h3>
<p class="text-warning mb-0">๊ฒฝ๊ณ </p>
</div>
<div class="col-4 text-center">
<h3 class="mb-0 display-6 text-danger">${criticalCount}</h3>
<p class="text-danger mb-0">์ฌ๊ฐ</p>
</div>
</div>
<div>
<canvas id="globalStatusChart" height="150"></canvas>
</div>
`;
// ์ํ ์๋ฆผ ์์ฑ ๋ฐ ํ์
this.updateStatusAlert(normalCount, warningCount, criticalCount);
// ์ฐจํธ ์
๋ฐ์ดํธ
const chartElement = document.getElementById('globalStatusChart');
if (!chartElement) {
console.error("Global status chart element not found.");
return;
}
const chartCtx = chartElement.getContext('2d');
if (!chartCtx) {
console.error("Global status chart context could not be obtained.");
return;
}
if (this.globalStatusChartInstance) {
this.globalStatusChartInstance.destroy();
}
if (normalCount === 0 && warningCount === 0 && criticalCount === 0) {
// ๋ฐ์ดํฐ๊ฐ ์๋ ๊ฒฝ์ฐ ๋น ์ฐจํธ๊ฐ ์๋ ๋ฉ์์ง ํ์
chartElement.parentElement.innerHTML = `
<div class="alert alert-info text-center">
<i class="fas fa-info-circle me-2"></i>
ํ์ํ ์๋ฒ ์ํ ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.
</div>
`;
return;
}
this.globalStatusChartInstance = new Chart(chartCtx, {
type: 'doughnut',
data: {
labels: ['์ ์', '๊ฒฝ๊ณ ', '์ฌ๊ฐ'],
datasets: [{
data: [normalCount, warningCount, criticalCount],
backgroundColor: [
'rgba(40, 167, 69, 0.7)', // ์ ์ (์ด๋ก ๊ณ์ด)
'rgba(253, 154, 20, 0.7)', // ๊ฒฝ๊ณ (์ฃผํฉ ๊ณ์ด)
'rgba(220, 53, 69, 0.7)' // ์ฌ๊ฐ (๋นจ๊ฐ ๊ณ์ด)
],
borderColor: [
'rgba(40, 167, 69, 1)',
'rgba(253, 154, 20, 1)',
'rgba(220, 53, 69, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
},
tooltip: {
callbacks: {
label: function(context) {
let label = context.label || '';
if (label) {
label += ': ';
}
if (context.parsed !== null) {
label += context.parsed + ' ๋';
}
return label;
}
}
}
}
}
});
// ์๋ฒ ์ ํ์ ์
๋ฐ์ดํธ
this.updateServerCount();
}
// ์๋ฒ ์ํ ์์ฝ ์๋ฆผ ์
๋ฐ์ดํธ
updateStatusAlert(normalCount, warningCount, criticalCount) {
const alertElement = document.getElementById('statusSummaryAlert');
if (!alertElement) return;
const iconContainer = alertElement.querySelector('.status-icon');
const messageContainer = alertElement.querySelector('.status-message');
if (!iconContainer || !messageContainer) return;
const totalServers = normalCount + warningCount + criticalCount;
// ์ํ ๊ฒฐ์ (์ต์
์ ์ํ๋ฅผ ๊ธฐ์ค์ผ๋ก)
let status = 'normal';
if (criticalCount > 0) status = 'critical';
else if (warningCount > 0) status = 'warning';
// ์๋ฆผ ํด๋์ค์ ์์ด์ฝ ์ค์
let alertClass, iconHTML, message;
switch(status) {
case 'critical':
alertClass = 'alert-danger';
iconHTML = '<i class="fas fa-exclamation-circle fa-2x"></i>';
if (criticalCount === 1) {
message = `<strong>๊ธด๊ธ ์ฃผ์ ํ์:</strong> 1๊ฐ ์๋ฒ๊ฐ ์ฌ๊ฐํ ์ํ์
๋๋ค. ์ฆ์ ํ์ธ์ด ํ์ํฉ๋๋ค.`;
} else {
message = `<strong>๊ธด๊ธ ์ฃผ์ ํ์:</strong> ${criticalCount}๊ฐ ์๋ฒ๊ฐ ์ฌ๊ฐํ ์ํ์
๋๋ค. ์ฆ์ ํ์ธ์ด ํ์ํฉ๋๋ค.`;
}
break;
case 'warning':
alertClass = 'alert-warning';
iconHTML = '<i class="fas fa-exclamation-triangle fa-2x"></i>';
if (warningCount === 1) {
message = `<strong>์ฃผ์:</strong> 1๊ฐ ์๋ฒ์ ๊ฒฝ๊ณ ์ํ๊ฐ ๊ฐ์ง๋์์ต๋๋ค. ์ํ๋ฅผ ํ์ธํด ์ฃผ์ธ์.`;
} else {
message = `<strong>์ฃผ์:</strong> ${warningCount}๊ฐ ์๋ฒ์ ๊ฒฝ๊ณ ์ํ๊ฐ ๊ฐ์ง๋์์ต๋๋ค. ์ํ๋ฅผ ํ์ธํด ์ฃผ์ธ์.`;
}
break;
default: // normal
alertClass = 'alert-success';
iconHTML = '<i class="fas fa-check-circle fa-2x"></i>';
message = `<strong>๋ชจ๋ ์ ์:</strong> ํ์ฌ ${totalServers}๊ฐ์ ์๋ฒ๊ฐ ๋ชจ๋ ์ ์ ์๋ ์ค์
๋๋ค.`;
}
// ์๋ฆผ ์
๋ฐ์ดํธ
alertElement.className = `alert d-flex align-items-center ${alertClass}`;
iconContainer.innerHTML = iconHTML;
messageContainer.innerHTML = message;
// ์ ๋๋ฉ์ด์
ํจ๊ณผ๋ก ํ์
alertElement.style.display = 'flex';
alertElement.style.opacity = '0';
alertElement.style.transform = 'translateY(-10px)';
// ๋ถ๋๋ฝ๊ฒ ํ์
setTimeout(() => {
alertElement.style.transition = 'all 0.5s ease';
alertElement.style.opacity = '1';
alertElement.style.transform = 'translateY(0)';
}, 100);
}
// ํ๋ฆฌ์
์ฟผ๋ฆฌ ์ฒ๋ฆฌ ๊ฐ์
processPresetQuery(query) {
if (!this.aiProcessor) return "AI ํ๋ก์ธ์๋ฅผ ์ด๊ธฐํํ ์ ์์ต๋๋ค.";
// ์๋ฒ ์ํ ๋ฐ์ดํฐ ์์ง
const criticalServers = this.serverData.filter(server => this.getServerStatus(server) === 'critical');
const warningServers = this.serverData.filter(server => this.getServerStatus(server) === 'warning');
const normalServers = this.serverData.filter(server => this.getServerStatus(server) === 'normal');
// AI ์ฅ์ ๋ณด๊ณ ์ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ (์์ ๊ฒฝ์ฐ)
let aiProblemsData = [];
if (this.problemsData && Array.isArray(this.problemsData)) {
aiProblemsData = [...this.problemsData];
} else if (typeof this.aiProcessor.detectProblems === 'function') {
try {
aiProblemsData = this.aiProcessor.detectProblems();
} catch (err) {
console.warn("AI ๋ฌธ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์ค ์ค๋ฅ ๋ฐ์:", err);
}
}
// CPU ๊ณผ๋ถํ ๊ด๋ จ ์ฟผ๋ฆฌ
if (query.includes("CPU ์ฌ์ฉ๋ฅ ์ด ๋์ ์๋ฒ") || query.includes("CPU ๊ณผ๋ถํ")) {
const highCpuServers = this.serverData.filter(server => server.cpu_usage >= this.thresholds.warning.cpu);
// ์๋ฒ ์ํ์ ๋ฐ๋ผ ์๋ต ํ์ ์กฐ์
if (highCpuServers.length === 0) {
return `### CPU ์ฌ์ฉ๋ฅ ๋ถ์ ๊ฒฐ๊ณผ
ํ์ฌ CPU ์ฌ์ฉ๋ฅ ์ด ๋์ ์๋ฒ๊ฐ ์์ต๋๋ค. ๋ชจ๋ ์๋ฒ๊ฐ ์ ์์ ์ธ CPU ์ฌ์ฉ๋ฅ ์ ๋ณด์ด๊ณ ์์ต๋๋ค.
**์ด ์๋ฒ ์**: ${this.serverData.length}๋
**ํ๊ท CPU ์ฌ์ฉ๋ฅ **: ${(this.serverData.reduce((acc, server) => acc + server.cpu_usage, 0) / this.serverData.length).toFixed(1)}%
๋ชจ๋ ์๋ฒ์ CPU ์ฌ์ฉ๋ฅ ์ด ์ ์ ๋ฒ์(${this.thresholds.warning.cpu}% ๋ฏธ๋ง) ๋ด์ ์์ต๋๋ค.`;
}
// ํด๋น ๋ฌธ์ ์ ๊ด๋ จ๋ AI ์ฅ์ ๋ณด๊ณ ์ ํญ๋ชฉ ์ฐพ๊ธฐ
const cpuRelatedProblems = aiProblemsData.filter(problem =>
problem.description && problem.description.toLowerCase().includes('cpu')
);
let response = `### CPU ์ฌ์ฉ๋ฅ ๋ถ์ ๊ฒฐ๊ณผ
CPU ์ฌ์ฉ๋ฅ ์ด ๋์ ์๋ฒ๊ฐ ${highCpuServers.length}๋ ๋ฐ๊ฒฌ๋์์ต๋๋ค.
`;
// ์ฌ๊ฐํ ์์ค(Critical)์ ์๋ฒ ๋จผ์ ํ์
const criticalCpuServers = highCpuServers.filter(server => server.cpu_usage >= this.thresholds.critical.cpu);
if (criticalCpuServers.length > 0) {
response += `#### ์ฌ๊ฐํ CPU ๊ณผ๋ถํ (${this.thresholds.critical.cpu}% ์ด์)
`;
criticalCpuServers.forEach(server => {
response += `- **${server.hostname}**: CPU ${server.cpu_usage}% (์ฌ๊ฐ)
- ํ๋ก์ธ์ค ์: ${server.process_count || '์ ๋ณด ์์'}
- ๋ถํ ํ๊ท : ${server.load_avg_1m || '์ ๋ณด ์์'}
`;
});
response += "\n";
}
// ๊ฒฝ๊ณ ์์ค(Warning)์ ์๋ฒ ํ์
const warningCpuServers = highCpuServers.filter(server =>
server.cpu_usage >= this.thresholds.warning.cpu &&
server.cpu_usage < this.thresholds.critical.cpu
);
if (warningCpuServers.length > 0) {
response += `#### ๊ฒฝ๊ณ ์์ค์ CPU ์ฌ์ฉ (${this.thresholds.warning.cpu}% ~ ${this.thresholds.critical.cpu-0.1}%)
`;
warningCpuServers.forEach(server => {
response += `- **${server.hostname}**: CPU ${server.cpu_usage}% (๊ฒฝ๊ณ )
- ํ๋ก์ธ์ค ์: ${server.process_count || '์ ๋ณด ์์'}
- ๋ถํ ํ๊ท : ${server.load_avg_1m || '์ ๋ณด ์์'}
`;
});
response += "\n";
}
// ๋ฌธ์ ์์ธ ๋ฐ ํด๊ฒฐ ๋ฐฉ์ (AI ์ฅ์ ๋ณด๊ณ ์์์ ๊ฐ์ ธ์ด)
response += "### ์์ธ ๋ถ์ ๋ฐ ์กฐ์น ๋ฐฉ์\n\n";
if (cpuRelatedProblems.length > 0) {
cpuRelatedProblems.forEach((problem, index) => {
if (index < 3) { // ์์ 3๊ฐ๋ง ํ์
response += `${index+1}. **${problem.serverHostname || '์ ์ฒด ์๋ฒ'}**: ${problem.description}\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: ${problem.solution || '์๋ฒ ๋ถํ ์์ธ ํ์ธ ํ์'}\n\n`;
}
});
} else {
response += `1. **๋์ CPU ์ฌ์ฉ๋ฅ ์์ธ**: ์๋ฒ์์ ๋ฆฌ์์ค๋ฅผ ๋ง์ด ์ฌ์ฉํ๋ ํ๋ก์ธ์ค๊ฐ ์คํ ์ค์ด๊ฑฐ๋, ๋์์ ๋ง์ ์์ฒญ์ด ์ฒ๋ฆฌ๋๊ณ ์์ ์ ์์ต๋๋ค.\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: 'top' ๋ช
๋ น์ด๋ก CPU๋ฅผ ๋ง์ด ์ฌ์ฉํ๋ ํ๋ก์ธ์ค๋ฅผ ํ์ธํ๊ณ , ๋ถํ์ํ ํ๋ก์ธ์ค ์ข
๋ฃ๋ ๋ถํ ๋ถ์ฐ์ ๊ณ ๋ คํ์ธ์.\n\n`;
response += `2. **์ฑ๋ฅ ๋ณ๋ชฉ ํ์**: CPU ์ฌ์ฉ๋ฅ ์ด ์ง์์ ์ผ๋ก ๋์ ๊ฒฝ์ฐ ์ฑ๋ฅ ๋ณ๋ชฉ ํ์์ด ๋ฐ์ํ ์ ์์ต๋๋ค.\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: ์๋ฒ ์ค์ผ์ผ ์
๋๋ ๋ก๋ ๋ฐธ๋ฐ์ฑ์ ํตํ ์ค์ผ์ผ ์์์ ๊ฒํ ํ์ธ์.\n\n`;
}
return response;
}
// ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ๊ด๋ จ ์ฟผ๋ฆฌ
else if (query.includes("๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋ง์ ์๋ฒ") || query.includes("๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ")) {
const highMemoryServers = this.serverData.filter(server => server.memory_usage_percent >= this.thresholds.warning.memory);
// ์๋ฒ ์ํ์ ๋ฐ๋ผ ์๋ต ํ์ ์กฐ์
if (highMemoryServers.length === 0) {
return `### ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋ถ์ ๊ฒฐ๊ณผ
ํ์ฌ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋์ ์๋ฒ๊ฐ ์์ต๋๋ค. ๋ชจ๋ ์๋ฒ๊ฐ ์ ์์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ๋ณด์ด๊ณ ์์ต๋๋ค.
**์ด ์๋ฒ ์**: ${this.serverData.length}๋
**ํ๊ท ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ **: ${(this.serverData.reduce((acc, server) => acc + server.memory_usage_percent, 0) / this.serverData.length).toFixed(1)}%
๋ชจ๋ ์๋ฒ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ ์ด ์ ์ ๋ฒ์(${this.thresholds.warning.memory}% ๋ฏธ๋ง) ๋ด์ ์์ต๋๋ค.`;
}
// ํด๋น ๋ฌธ์ ์ ๊ด๋ จ๋ AI ์ฅ์ ๋ณด๊ณ ์ ํญ๋ชฉ ์ฐพ๊ธฐ
const memoryRelatedProblems = aiProblemsData.filter(problem =>
problem.description && (
problem.description.toLowerCase().includes('memory') ||
problem.description.toLowerCase().includes('๋ฉ๋ชจ๋ฆฌ')
)
);
let response = `### ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋ถ์ ๊ฒฐ๊ณผ
๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋์ ์๋ฒ๊ฐ ${highMemoryServers.length}๋ ๋ฐ๊ฒฌ๋์์ต๋๋ค.
`;
// ์ฌ๊ฐํ ์์ค(Critical)์ ์๋ฒ ๋จผ์ ํ์
const criticalMemServers = highMemoryServers.filter(server => server.memory_usage_percent >= this.thresholds.critical.memory);
if (criticalMemServers.length > 0) {
response += `#### ์ฌ๊ฐํ ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ (${this.thresholds.critical.memory}% ์ด์)
`;
criticalMemServers.forEach(server => {
response += `- **${server.hostname}**: ๋ฉ๋ชจ๋ฆฌ ${server.memory_usage_percent}% (์ฌ๊ฐ)
- ์ด ๋ฉ๋ชจ๋ฆฌ: ${server.memory_total || '์ ๋ณด ์์'}
- ์ฌ์ฉ ๋ฉ๋ชจ๋ฆฌ: ${server.memory_used || '์ ๋ณด ์์'}
`;
});
response += "\n";
}
// ๊ฒฝ๊ณ ์์ค(Warning)์ ์๋ฒ ํ์
const warningMemServers = highMemoryServers.filter(server =>
server.memory_usage_percent >= this.thresholds.warning.memory &&
server.memory_usage_percent < this.thresholds.critical.memory
);
if (warningMemServers.length > 0) {
response += `#### ๊ฒฝ๊ณ ์์ค์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ (${this.thresholds.warning.memory}% ~ ${this.thresholds.critical.memory-0.1}%)
`;
warningMemServers.forEach(server => {
response += `- **${server.hostname}**: ๋ฉ๋ชจ๋ฆฌ ${server.memory_usage_percent}% (๊ฒฝ๊ณ )
- ์ด ๋ฉ๋ชจ๋ฆฌ: ${server.memory_total || '์ ๋ณด ์์'}
- ์ฌ์ฉ ๋ฉ๋ชจ๋ฆฌ: ${server.memory_used || '์ ๋ณด ์์'}
`;
});
response += "\n";
}
// ๋ฌธ์ ์์ธ ๋ฐ ํด๊ฒฐ ๋ฐฉ์ (AI ์ฅ์ ๋ณด๊ณ ์์์ ๊ฐ์ ธ์ด)
response += "### ์์ธ ๋ถ์ ๋ฐ ์กฐ์น ๋ฐฉ์\n\n";
if (memoryRelatedProblems.length > 0) {
memoryRelatedProblems.forEach((problem, index) => {
if (index < 3) { // ์์ 3๊ฐ๋ง ํ์
response += `${index+1}. **${problem.serverHostname || '์ ์ฒด ์๋ฒ'}**: ${problem.description}\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: ${problem.solution || '๋ฉ๋ชจ๋ฆฌ ๋์ ๋๋ ๊ณผ๋ค ์ฌ์ฉ ํ๋ก์ธ์ค ํ์ธ ํ์'}\n\n`;
}
});
} else {
response += `1. **๋์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ ์์ธ**: ์๋ฒ์์ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ์๊ฑฐ๋, ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ง์ด ์ฌ์ฉํ๋ ์ ํ๋ฆฌ์ผ์ด์
์ด ์คํ ์ค์ผ ์ ์์ต๋๋ค.\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: 'free -m', 'top' ๋ช
๋ น์ด๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ง์ด ์ฌ์ฉํ๋ ํ๋ก์ธ์ค๋ฅผ ํ์ธํ๊ณ , ํ์์ ์ฌ์์ํ์ธ์.\n\n`;
response += `2. **์ค์ ์ฌ์ฉ ํ์ธ**: ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ์ ์ค์ ์ฌ์ฉ๋์ด ์ฆ๊ฐํ ์ ์์ต๋๋ค.\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: 'vmstat' ๋ช
๋ น์ด๋ก ์ค์ ์ฌ์ฉ๋์ ํ์ธํ๊ณ , ๋ฉ๋ชจ๋ฆฌ ์ฆ์ค์ ๊ณ ๋ คํ์ธ์.\n\n`;
}
return response;
}
// ์๋น์ค ์ค๋จ ๊ด๋ จ ์ฟผ๋ฆฌ
else if (query.includes("์๋น์ค๊ฐ ์ค๋จ๋ ์๋ฒ")) {
// ์๋น์ค ์ค๋จ ์๋ฒ ์ฐพ๊ธฐ
const stoppedServiceServers = this.serverData.filter(server =>
server.services && Object.values(server.services).some(status => status === 'stopped')
);
// ์๋ฒ ์ํ์ ๋ฐ๋ผ ์๋ต ํ์ ์กฐ์
if (stoppedServiceServers.length === 0) {
return `### ์๋น์ค ์ํ ๋ถ์ ๊ฒฐ๊ณผ
ํ์ฌ ์ค๋จ๋ ์๋น์ค๊ฐ ์๋ ์๋ฒ๊ฐ ์์ต๋๋ค. ๋ชจ๋ ์๋ฒ์ ์๋น์ค๊ฐ ์ ์ ์๋ ์ค์
๋๋ค.
**์ด ์๋ฒ ์**: ${this.serverData.length}๋
๋ชจ๋ ์๋ฒ์์ ์๋น์ค๊ฐ ์ ์์ ์ผ๋ก ์คํ ์ค์
๋๋ค.`;
}
// ํด๋น ๋ฌธ์ ์ ๊ด๋ จ๋ AI ์ฅ์ ๋ณด๊ณ ์ ํญ๋ชฉ ์ฐพ๊ธฐ
const serviceRelatedProblems = aiProblemsData.filter(problem =>
problem.description && (
problem.description.toLowerCase().includes('service') ||
problem.description.toLowerCase().includes('์๋น์ค')
)
);
let response = `### ์๋น์ค ์ํ ๋ถ์ ๊ฒฐ๊ณผ
์๋น์ค๊ฐ ์ค๋จ๋ ์๋ฒ๊ฐ ${stoppedServiceServers.length}๋ ๋ฐ๊ฒฌ๋์์ต๋๋ค.
#### ์ค๋จ๋ ์๋น์ค๊ฐ ์๋ ์๋ฒ
`;
stoppedServiceServers.forEach(server => {
const stoppedServices = Object.entries(server.services)
.filter(([_, status]) => status === 'stopped')
.map(([name, _]) => name);
response += `- **${server.hostname}**:
- ์ค๋จ๋ ์๋น์ค: ${stoppedServices.join(', ')}
- ์๋ฒ ์ํ: ${this.getStatusLabel(this.getServerStatus(server))}
`;
});
// ๋ฌธ์ ์์ธ ๋ฐ ํด๊ฒฐ ๋ฐฉ์ (AI ์ฅ์ ๋ณด๊ณ ์์์ ๊ฐ์ ธ์ด)
response += "\n### ์์ธ ๋ถ์ ๋ฐ ์๋น์ค ์ฌ์์ ๋ฐฉ๋ฒ\n\n";
if (serviceRelatedProblems.length > 0) {
serviceRelatedProblems.forEach((problem, index) => {
if (index < 3) { // ์์ 3๊ฐ๋ง ํ์
response += `${index+1}. **${problem.serverHostname || '์ ์ฒด ์๋ฒ'}**: ${problem.description}\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: ${problem.solution || '์๋น์ค ์ฌ์์ ํ์'}\n\n`;
}
});
} else {
response += `1. **์๋น์ค ์ค๋จ ์์ธ**: ์๋น์ค ์ถฉ๋, ๋ฆฌ์์ค ๋ถ์กฑ, ๋๋ ๊ฐ์ ์ข
๋ฃ๋ก ์ธํด ์๋น์ค๊ฐ ์ค๋จ๋์์ ์ ์์ต๋๋ค.\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: 'systemctl restart SERVICE_NAME' ๋ช
๋ น์ด๋ก ํด๋น ์๋น์ค๋ฅผ ์ฌ์์ํ์ธ์.\n\n`;
response += `2. **๋ก๊ทธ ํ์ธ**: ์ค๋จ๋ ์์ธ์ ํ์
ํ๊ธฐ ์ํด ๋ก๊ทธ ํ์ธ์ด ํ์ํฉ๋๋ค.\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: 'journalctl -u SERVICE_NAME' ๋ช
๋ น์ด๋ก ์๋น์ค ๋ก๊ทธ๋ฅผ ํ์ธํ์ธ์.\n\n`;
}
return response;
}
// ๋์คํฌ ๊ณต๊ฐ ๋ถ์กฑ ๊ด๋ จ ์ฟผ๋ฆฌ
else if (query.includes("๋์คํฌ ๊ณต๊ฐ์ด ๋ถ์กฑํ ์๋ฒ")) {
const highDiskServers = this.serverData.filter(server =>
server.disk &&
server.disk.length > 0 &&
server.disk[0].disk_usage_percent >= this.thresholds.warning.disk
);
// ์๋ฒ ์ํ์ ๋ฐ๋ผ ์๋ต ํ์ ์กฐ์
if (highDiskServers.length === 0) {
return `### ๋์คํฌ ๊ณต๊ฐ ๋ถ์ ๊ฒฐ๊ณผ
ํ์ฌ ๋์คํฌ ๊ณต๊ฐ์ด ๋ถ์กฑํ ์๋ฒ๊ฐ ์์ต๋๋ค. ๋ชจ๋ ์๋ฒ๊ฐ ์ถฉ๋ถํ ๋์คํฌ ๊ณต๊ฐ์ ๋ณด์ ํ๊ณ ์์ต๋๋ค.
**์ด ์๋ฒ ์**: ${this.serverData.length}๋
**ํ๊ท ๋์คํฌ ์ฌ์ฉ๋ฅ **: ${(this.serverData.reduce((acc, server) => acc + (server.disk && server.disk.length > 0 ? server.disk[0].disk_usage_percent : 0), 0) / this.serverData.length).toFixed(1)}%
๋ชจ๋ ์๋ฒ์ ๋์คํฌ ์ฌ์ฉ๋ฅ ์ด ์ ์ ๋ฒ์(${this.thresholds.warning.disk}% ๋ฏธ๋ง) ๋ด์ ์์ต๋๋ค.`;
}
// ํด๋น ๋ฌธ์ ์ ๊ด๋ จ๋ AI ์ฅ์ ๋ณด๊ณ ์ ํญ๋ชฉ ์ฐพ๊ธฐ
const diskRelatedProblems = aiProblemsData.filter(problem =>
problem.description && (
problem.description.toLowerCase().includes('disk') ||
problem.description.toLowerCase().includes('๋์คํฌ') ||
problem.description.toLowerCase().includes('๊ณต๊ฐ')
)
);
let response = `### ๋์คํฌ ๊ณต๊ฐ ๋ถ์ ๊ฒฐ๊ณผ
๋์คํฌ ๊ณต๊ฐ์ด ๋ถ์กฑํ ์๋ฒ๊ฐ ${highDiskServers.length}๋ ๋ฐ๊ฒฌ๋์์ต๋๋ค.
`;
// ์ฌ๊ฐํ ์์ค(Critical)์ ์๋ฒ ๋จผ์ ํ์
const criticalDiskServers = highDiskServers.filter(server =>
server.disk[0].disk_usage_percent >= this.thresholds.critical.disk
);
if (criticalDiskServers.length > 0) {
response += `#### ์ฌ๊ฐํ ๋์คํฌ ๊ณต๊ฐ ๋ถ์กฑ (${this.thresholds.critical.disk}% ์ด์)
`;
criticalDiskServers.forEach(server => {
response += `- **${server.hostname}**: ๋์คํฌ ${server.disk[0].disk_usage_percent}% (์ฌ๊ฐ)
- ๋ง์ดํธ ์ง์ : ${server.disk[0].mount || '/'}
- ์ด ์ฉ๋: ${server.disk[0].disk_total || '์ ๋ณด ์์'}
- ์ฌ์ฉ ์ฉ๋: ${server.disk[0].disk_used || '์ ๋ณด ์์'}
`;
});
response += "\n";
}
// ๊ฒฝ๊ณ ์์ค(Warning)์ ์๋ฒ ํ์
const warningDiskServers = highDiskServers.filter(server =>
server.disk[0].disk_usage_percent >= this.thresholds.warning.disk &&
server.disk[0].disk_usage_percent < this.thresholds.critical.disk
);
if (warningDiskServers.length > 0) {
response += `#### ๊ฒฝ๊ณ ์์ค์ ๋์คํฌ ์ฌ์ฉ (${this.thresholds.warning.disk}% ~ ${this.thresholds.critical.disk-0.1}%)
`;
warningDiskServers.forEach(server => {
response += `- **${server.hostname}**: ๋์คํฌ ${server.disk[0].disk_usage_percent}% (๊ฒฝ๊ณ )
- ๋ง์ดํธ ์ง์ : ${server.disk[0].mount || '/'}
- ์ด ์ฉ๋: ${server.disk[0].disk_total || '์ ๋ณด ์์'}
- ์ฌ์ฉ ์ฉ๋: ${server.disk[0].disk_used || '์ ๋ณด ์์'}
`;
});
response += "\n";
}
// ๋ฌธ์ ์์ธ ๋ฐ ํด๊ฒฐ ๋ฐฉ์ (AI ์ฅ์ ๋ณด๊ณ ์์์ ๊ฐ์ ธ์ด)
response += "### ์์ธ ๋ถ์ ๋ฐ ์กฐ์น ๋ฐฉ์\n\n";
if (diskRelatedProblems.length > 0) {
diskRelatedProblems.forEach((problem, index) => {
if (index < 3) { // ์์ 3๊ฐ๋ง ํ์
response += `${index+1}. **${problem.serverHostname || '์ ์ฒด ์๋ฒ'}**: ${problem.description}\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: ${problem.solution || '๋ถํ์ํ ํ์ผ ์ ๋ฆฌ ํ์'}\n\n`;
}
});
} else {
response += `1. **๋์คํฌ ๊ณต๊ฐ ๋ถ์กฑ ์์ธ**: ๋ก๊ทธ ํ์ผ ์ฆ๊ฐ, ์์ ํ์ผ ๋์ , ๋๋ ๋ฐ์ดํฐ ์ฆ๊ฐ๋ก ์ธํด ๋์คํฌ ๊ณต๊ฐ์ด ๋ถ์กฑํ ์ ์์ต๋๋ค.\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: 'du -h --max-depth=1 /path' ๋ช
๋ น์ด๋ก ์ฉ๋์ด ํฐ ๋๋ ํ ๋ฆฌ๋ฅผ ์ฐพ๊ณ , ๋ถํ์ํ ํ์ผ์ ์ ๋ฆฌํ์ธ์.\n\n`;
response += `2. **๋ก๊ทธ ์ ๋ฆฌ**: ์ค๋๋ ๋ก๊ทธ ํ์ผ์ด ๋ง์ ๊ณต๊ฐ์ ์ฐจ์งํ ์ ์์ต๋๋ค.\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: '/var/log' ๋๋ ํ ๋ฆฌ์ ์ค๋๋ ๋ก๊ทธ ํ์ผ์ ์ ๋ฆฌํ๊ณ , logrotate ์ค์ ์ ํ์ธํ์ธ์.\n\n`;
}
return response;
}
// ์ ์ ์๋ฒ ๋ชฉ๋ก ํ์
else if (query.includes("์ ์ ์๋ ์ค์ธ ์๋ฒ ๋ชฉ๋ก")) {
if (normalServers.length === 0) {
return "### ์ ์ ์๋ฒ ๋ชฉ๋ก\n\nํ์ฌ ๋ชจ๋ ์๋ฒ์ ๋ฌธ์ ๊ฐ ์์ด ์ ์ ์๋ ์ค์ธ ์๋ฒ๊ฐ ์์ต๋๋ค.";
}
let response = `### ์ ์ ์๋ ์ค์ธ ์๋ฒ ๋ชฉ๋ก (์ด ${normalServers.length}๋)\n\n`;
normalServers.forEach(server => {
response += `#### ${server.hostname}\n`;
response += `- CPU: ${server.cpu_usage}% (์ ์)\n`;
response += `- ๋ฉ๋ชจ๋ฆฌ: ${server.memory_usage_percent}% (์ ์)\n`;
response += `- ๋์คํฌ: ${server.disk && server.disk.length > 0 ? server.disk[0].disk_usage_percent : 0}% (์ ์)\n`;
response += `- ์
ํ์: ${server.uptime || '์ ๋ณด ์์'}\n\n`;
});
response += "๋ชจ๋ ๋ฆฌ์์ค๊ฐ ์ ์ ์๊ณ๊ฐ ์ด๋ด์์ ์๋ ์ค์ด๋ฉฐ, ํน๋ณํ ์กฐ์น๊ฐ ํ์ํ์ง ์์ต๋๋ค.";
return response;
}
// ์ ์ฒด ์๋ฒ ์ํ ์์ฝ ๋ณด๊ณ ์
else if (query.includes("์ ์ฒด ์๋ฒ ์ํ ์์ฝ")) {
let response = `### ์ ์ฒด ์๋ฒ ์ํ ์์ฝ ๋ณด๊ณ ์\n\n`;
response += `**์ด ์๋ฒ ์**: ${this.serverData.length}๋\n`;
response += `- ์ ์ ์๋ฒ: ${normalServers.length}๋\n`;
response += `- ๊ฒฝ๊ณ ์ํ: ${warningServers.length}๋\n`;
response += `- ์ฌ๊ฐ ์ํ: ${criticalServers.length}๋\n\n`;
// ์๊ณ๊ฐ ์ด๊ณผ ํํฉ
const highCpuCount = this.serverData.filter(s => s.cpu_usage >= this.thresholds.warning.cpu).length;
const highMemCount = this.serverData.filter(s => s.memory_usage_percent >= this.thresholds.warning.memory).length;
const highDiskCount = this.serverData.filter(s =>
s.disk && s.disk.length > 0 && s.disk[0].disk_usage_percent >= this.thresholds.warning.disk
).length;
const stoppedServiceCount = this.serverData.filter(s =>
s.services && Object.values(s.services).some(status => status === 'stopped')
).length;
response += `### ๋ฆฌ์์ค ์ฌ์ฉ ํํฉ\n\n`;
response += `- CPU ์๊ณ์น ์ด๊ณผ: ${highCpuCount}๋\n`;
response += `- ๋ฉ๋ชจ๋ฆฌ ์๊ณ์น ์ด๊ณผ: ${highMemCount}๋\n`;
response += `- ๋์คํฌ ์๊ณ์น ์ด๊ณผ: ${highDiskCount}๋\n`;
response += `- ์๋น์ค ์ค๋จ ์๋ฒ: ${stoppedServiceCount}๋\n\n`;
// ์ฌ๊ฐํ ๋ฌธ์ ์๋ฒ ๋ชฉ๋ก (์ต๋ 3๋)
if (criticalServers.length > 0) {
response += `### ์ฌ๊ฐํ ์ํ์ ์๋ฒ (์์ ${Math.min(3, criticalServers.length)}๋)\n\n`;
criticalServers.slice(0, 3).forEach(server => {
response += `#### ${server.hostname}\n`;
// ์๋ฒ์ ๋ฌธ์ ์์ธ ํ์
const issues = [];
if (server.cpu_usage >= this.thresholds.critical.cpu) {
issues.push(`CPU ์ฌ์ฉ๋ฅ ${server.cpu_usage}% (์๊ณ์น ${this.thresholds.critical.cpu}%)`);
}
if (server.memory_usage_percent >= this.thresholds.critical.memory) {
issues.push(`๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ ${server.memory_usage_percent}% (์๊ณ์น ${this.thresholds.critical.memory}%)`);
}
if (server.disk && server.disk.length > 0 && server.disk[0].disk_usage_percent >= this.thresholds.critical.disk) {
issues.push(`๋์คํฌ ์ฌ์ฉ๋ฅ ${server.disk[0].disk_usage_percent}% (์๊ณ์น ${this.thresholds.critical.disk}%)`);
}
if (server.services && Object.values(server.services).some(status => status === 'stopped')) {
const stoppedServices = Object.entries(server.services)
.filter(([_, status]) => status === 'stopped')
.map(([name, _]) => name);
issues.push(`์ค๋จ๋ ์๋น์ค: ${stoppedServices.join(', ')}`);
}
issues.forEach(issue => response += `- ${issue}\n`);
response += '\n';
});
}
// ๊ด๋ จ AI ๋ฌธ์ ํญ๋ชฉ ์ถ๊ฐ
if (aiProblemsData.length > 0) {
response += `### AI ๋ถ์ ๋ฌธ์ ํญ๋ชฉ (์์ ${Math.min(3, aiProblemsData.length)}๊ฐ)\n\n`;
aiProblemsData.slice(0, 3).forEach((problem, index) => {
response += `${index+1}. **${problem.serverHostname || '์ ์ฒด ์๋ฒ'}**: ${problem.description}\n`;
response += ` - ์ฌ๊ฐ๋: ${problem.severity}\n`;
response += ` - ๊ถ์ฅ ์กฐ์น: ${problem.solution || '๋ฌธ์ ์์ธ ๋ถ์ ํ์'}\n\n`;
});
}
return response;
}
// ๊ธฐ๋ณธ AI ํ๋ก์ธ์ ํธ์ถ
return this.aiProcessor.processQuery(query);
}
processAIQuery() {
if (!this.aiProcessor) return;
const queryInput = document.getElementById('queryInput');
if (!queryInput) {
console.error("์ฟผ๋ฆฌ ์
๋ ฅ ์์(queryInput)๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.");
return;
}
const query = queryInput.value.trim();
if (!query) return;
const queryLoadingElement = document.getElementById('queryLoading');
const queryResultElement = document.getElementById('queryResult');
const queryResultContent = document.getElementById('queryResultContent');
// ์๋ต ์์ญ ๋ณด์ด๊ธฐ
if (queryLoadingElement) queryLoadingElement.classList.add('active');
if (queryResultElement) queryResultElement.style.display = 'none';
// ํ๋ฆฌ์
๊ธฐ๋ฐ ์๋ต ์ฒ๋ฆฌ (์์ฒด ์ฒ๋ฆฌ)
const isPresetQuery =
query.includes("CPU ์ฌ์ฉ๋ฅ ์ด ๋์ ์๋ฒ") ||
query.includes("CPU ๊ณผ๋ถํ") ||
query.includes("๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋ง์ ์๋ฒ") ||
query.includes("๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ") ||
query.includes("์๋น์ค๊ฐ ์ค๋จ๋ ์๋ฒ") ||
query.includes("๋์คํฌ ๊ณต๊ฐ์ด ๋ถ์กฑํ ์๋ฒ") ||
query.includes("์ ์ ์๋ ์ค์ธ ์๋ฒ ๋ชฉ๋ก") ||
query.includes("์ ์ฒด ์๋ฒ ์ํ ์์ฝ");
if (isPresetQuery) {
const result = this.processPresetQuery(query);
if (result) {
if (queryResultContent && queryResultElement) {
queryResultContent.innerHTML = result;
queryResultElement.classList.add('active');
queryResultElement.style.display = 'block';
}
if (queryLoadingElement) queryLoadingElement.classList.remove('active');
return;
}
}
// AI ์ง์ ์ฒ๋ฆฌ (์๋ฒ ์ํ ๋ฐ์ดํฐ์ AI ๋ฌธ์ ๋ฐ์ดํฐ ์ฐ๋)
let enhancedQuery = query;
// AI ์๋ ์ฅ์ ๋ณด๊ณ ์ ๋ฐ์ดํฐ๊ฐ ์๋ค๋ฉด ์ฟผ๋ฆฌ์ ์ถ๊ฐ ์ปจํ
์คํธ ์ ๊ณต
if (this.problemsData && this.problemsData.length > 0) {
const relevantProblems = this.findRelevantProblems(query);
if (relevantProblems.length > 0) {
enhancedQuery += "\n\n๊ด๋ จ ๋ฌธ์ ๋ฐ์ดํฐ:\n";
relevantProblems.forEach(problem => {
enhancedQuery += `- ${problem.serverHostname || '์ ์ฒด ์๋ฒ'}: ${problem.description} (์ฌ๊ฐ๋: ${problem.severity})\n`;
if (problem.solution) {
enhancedQuery += ` ํด๊ฒฐ์ฑ
: ${problem.solution}\n`;
}
});
}
}
// ์๋ฒ ์ํ ๋ฐ์ดํฐ ํต๊ณ ์ถ๊ฐ
const criticalServers = this.serverData.filter(server => this.getServerStatus(server) === 'critical').length;
const warningServers = this.serverData.filter(server => this.getServerStatus(server) === 'warning').length;
const normalServers = this.serverData.filter(server => this.getServerStatus(server) === 'normal').length;
enhancedQuery += `\n\n์๋ฒ ํํฉ: ์ด ${this.serverData.length}๋ (์ ์: ${normalServers}, ๊ฒฝ๊ณ : ${warningServers}, ์ฌ๊ฐ: ${criticalServers})`;
this.aiProcessor.processQuery(enhancedQuery)
.then(response => {
if (queryResultContent && queryResultElement) {
queryResultContent.innerHTML = response;
queryResultElement.classList.add('active');
queryResultElement.style.display = 'block';
}
})
.catch(error => {
if (queryResultContent && queryResultElement) {
queryResultContent.innerHTML = `์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: ${error.message}`;
queryResultElement.classList.add('active');
queryResultElement.style.display = 'block';
}
})
.finally(() => {
if (queryLoadingElement) queryLoadingElement.classList.remove('active');
});
}
// ์ฟผ๋ฆฌ์ ๊ด๋ จ๋ ๋ฌธ์ ์ฐพ๊ธฐ
findRelevantProblems(query) {
if (!this.problemsData || !Array.isArray(this.problemsData)) {
return [];
}
const keywords = query.toLowerCase().split(/\s+/);
return this.problemsData.filter(problem => {
if (!problem.description) return false;
const description = problem.description.toLowerCase();
return keywords.some(keyword =>
keyword.length > 3 && description.includes(keyword)
);
});
}
downloadErrorReport() {
if (!this.aiProcessor) return;
let report = '';
try {
// generateErrorReport ๋ฉ์๋๊ฐ ์กด์ฌํ๊ณ ํธ์ถ ๊ฐ๋ฅํ์ง ํ์ธ
if (typeof this.aiProcessor.generateErrorReport === 'function') {
report = this.aiProcessor.generateErrorReport();
} else {
console.warn("AI ํ๋ก์ธ์์ generateErrorReport ๋ฉ์๋๊ฐ ์์ต๋๋ค. ๊ธฐ๋ณธ ๋ณด๊ณ ์๋ฅผ ์์ฑํฉ๋๋ค.");
// ๊ธฐ๋ณธ ์ฅ์ ๋ณด๊ณ ์ ์์ฑ ๋ก์ง
report = '# ์๋ฒ ์ํ ๋ณด๊ณ ์\n\n';
report += `์์ฑ ์๊ฐ: ${new Date().toLocaleString()}\n\n`;
// ์๋ฒ ์ํ์ ๋ฐ๋ผ ์์ฝ ์ ๋ณด ์์ฑ
const criticalServers = this.serverData.filter(s => this.getServerStatus(s) === 'critical');
const warningServers = this.serverData.filter(s => this.getServerStatus(s) === 'warning');
const normalServers = this.serverData.filter(s => this.getServerStatus(s) === 'normal');
report += `## ์๋ฒ ์ํ ์์ฝ\n\n`;
report += `- ์ด ์๋ฒ ์: ${this.serverData.length}\n`;
report += `- ์ ์: ${normalServers.length}\n`;
report += `- ๊ฒฝ๊ณ : ${warningServers.length}\n`;
report += `- ์ฌ๊ฐ: ${criticalServers.length}\n\n`;
// ๋ฌธ์ ์ํ์ ์๋ฒ์ ๋ํ ์ธ๋ถ ์ ๋ณด
if (criticalServers.length > 0) {
report += '## ์ฌ๊ฐํ ์ํ์ ์๋ฒ\n\n';
criticalServers.forEach(server => {
report += `### ${server.hostname}\n`;
report += `- CPU: ${server.cpu_usage}%\n`;
report += `- ๋ฉ๋ชจ๋ฆฌ: ${server.memory_usage_percent}%\n`;
report += `- ๋์คํฌ: ${server.disk[0].disk_usage_percent}%\n`;
if (server.errors && server.errors.length > 0) {
report += `- ์ค๋ฅ: ${server.errors.join(', ')}\n`;
}
report += `\n`;
});
}
if (warningServers.length > 0) {
report += '## ๊ฒฝ๊ณ ์ํ์ ์๋ฒ\n\n';
warningServers.forEach(server => {
report += `### ${server.hostname}\n`;
report += `- CPU: ${server.cpu_usage}%\n`;
report += `- ๋ฉ๋ชจ๋ฆฌ: ${server.memory_usage_percent}%\n`;
report += `- ๋์คํฌ: ${server.disk[0].disk_usage_percent}%\n`;
if (server.errors && server.errors.length > 0) {
report += `- ์ค๋ฅ: ${server.errors.join(', ')}\n`;
}
report += `\n`;
});
}
}
} catch (e) {
console.error("์ฅ์ ๋ณด๊ณ ์ ์์ฑ ์ค ์ค๋ฅ ๋ฐ์:", e);
report = '# ์ค๋ฅ ๋ฐ์\n\n์ฅ์ ๋ณด๊ณ ์๋ฅผ ์์ฑํ๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.\n\n' + e.message;
}
const blob = new Blob([report], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `server_error_report_${new Date().toISOString().slice(0, 10)}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// ์ ํธ๋ฆฌํฐ ํจ์
getServerStatus(server) {
// AI Processor์ getEffectiveServerStatus๋ฅผ ์ฌ์ฉํ์ฌ ์ํ ๊ฒฐ์
if (this.aiProcessor && typeof this.aiProcessor.getEffectiveServerStatus === 'function') {
try {
return this.aiProcessor.getEffectiveServerStatus(server);
} catch (e) {
// ์๋ฌ ๋ฐ์ ์ ํ ๋ฒ๋ง ๊ฒฝ๊ณ ์ถ๋ ฅ (fallback ๋ก์ง ์ํ)
if (!this._hasLoggedAIProcessorError) {
console.error("Error calling aiProcessor.getEffectiveServerStatus:", e);
this._hasLoggedAIProcessorError = true;
}
}
} else if (!this._hasLoggedNoAIProcessor) {
// ๊ฒฝ๊ณ ๋ฉ์์ง๋ฅผ ํ ๋ฒ๋ง ์ถ๋ ฅ
console.warn("AIProcessor ๋๋ getEffectiveServerStatus๊ฐ ์์ด ๊ธฐ๋ณธ ์ํ ํ๋จ ๋ก์ง์ ์ฌ์ฉํฉ๋๋ค.");
this._hasLoggedNoAIProcessor = true;
}
// ํด๋ฐฑ ๋ก์ง (AI Processor ์ฌ์ฉ ๋ถ๊ฐ ๋๋ ์๋ฌ ์)
// ๋ฆฌ์์ค ์ฌ์ฉ๋ฅ ๊ธฐ๋ฐ ๋ช
ํํ ๊ธฐ์ค์ผ๋ก ์ํ ํ๋จ
// 1. Critical ์กฐ๊ฑด ํ๋จ
if (server.cpu_usage >= this.thresholds.critical.cpu ||
server.memory_usage_percent >= this.thresholds.critical.memory ||
(server.disk && server.disk.length > 0 && server.disk[0].disk_usage_percent >= this.thresholds.critical.disk)) {
return 'critical';
}
// 2. ์ค๋ฅ ๋ฉ์์ง ๊ธฐ๋ฐ Critical ํ๋จ
const hasCriticalError = server.errors && server.errors.some(err =>
typeof err === 'string' && err.toLowerCase().includes('critical'));
if (hasCriticalError) {
return 'critical';
}
// 3. ์๋น์ค ์ค๋จ ๊ธฐ๋ฐ Critical ํ๋จ
const hasStoppedService = server.services && Object.values(server.services).includes('stopped');
if (hasStoppedService) {
return 'critical';
}
// 4. Warning ์กฐ๊ฑด ํ๋จ
if (server.cpu_usage >= this.thresholds.warning.cpu ||
server.memory_usage_percent >= this.thresholds.warning.memory ||
(server.disk && server.disk.length > 0 && server.disk[0].disk_usage_percent >= this.thresholds.warning.disk)) {
return 'warning';
}
// 5. ์ค๋ฅ ๋ฉ์์ง ๊ธฐ๋ฐ Warning ํ๋จ
const hasWarningError = server.errors && server.errors.some(err =>
typeof err === 'string' && (err.toLowerCase().includes('warning') || err.toLowerCase().includes('error')));
if (hasWarningError) {
return 'warning';
}
// 6. ์ ์กฐ๊ฑด์ ํด๋นํ์ง ์์ผ๋ฉด normal ์ํ
return 'normal';
}
getResourceStatus(value, type = 'generic') {
// ๋ฆฌ์์ค ์ ํ์ ๋ฐ๋ฅธ ์๊ณ๊ฐ ์ ์ฉ
const criticalThreshold = type in this.thresholds.critical
? this.thresholds.critical[type]
: this.thresholds.critical.cpu;
const warningThreshold = type in this.thresholds.warning
? this.thresholds.warning[type]
: this.thresholds.warning.cpu;
if (value >= criticalThreshold) return 'critical';
if (value >= warningThreshold) return 'warning';
return 'normal';
}
getStatusLabel(status) {
switch(status) {
case 'normal': return '์ ์';
case 'warning': return '๊ฒฝ๊ณ ';
case 'critical': return '์ฌ๊ฐ';
default: return '์ ์ ์์';
}
}
// ์๋ฒ ์ํ์ ๋ฐ๋ฅธ ๋ถํธ์คํธ๋ฉ ์์ ํด๋์ค ๋ฐํ
getStatusColorClass(status) {
switch(status) {
case 'normal': return 'success';
case 'warning': return 'warning';
case 'critical': return 'danger';
default: return 'secondary';
}
}
getChartColor(value, type = 'generic') {
const status = this.getResourceStatus(value, type);
switch(status) {
case 'critical': return 'rgba(220, 53, 69, 0.7)'; // ์ฌ๊ฐ
case 'warning': return 'rgba(253, 154, 20, 0.7)'; // ๊ฒฝ๊ณ
default: return 'rgba(40, 167, 69, 0.7)'; // ์ ์
}
}
formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// ๋ชจ๋ ๋ฌธ์ ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ชจ๋ฌ ํ์
showAllProblems() {
if (!this.aiProcessor) {
alert('AI ํ๋ก์ธ์๊ฐ ์ด๊ธฐํ๋์ง ์์ ๋ฌธ์ ๋ชฉ๋ก์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.');
return;
}
// ๋ฌธ์ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
let problems = [];
try {
if (typeof this.aiProcessor.detectProblems === 'function') {
problems = this.aiProcessor.detectProblems();
} else {
console.warn("AI ํ๋ก์ธ์์ detectProblems ๋ฉ์๋๊ฐ ์์ต๋๋ค.");
alert('๋ฌธ์ ๋ชฉ๋ก์ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค.');
return;
}
// ๊ฒฐ๊ณผ๊ฐ ๋ฐฐ์ด์ด ์๋๊ฑฐ๋ undefined์ธ ๊ฒฝ์ฐ ๋น ๋ฐฐ์ด๋ก ์ฒ๋ฆฌ
if (!Array.isArray(problems)) {
console.warn("detectProblems() ํจ์๊ฐ ๋ฐฐ์ด์ ๋ฐํํ์ง ์์์ต๋๋ค.");
problems = [];
}
// Normal ์ํ๋ ์ ์ธ
problems = problems.filter(p => p && (p.severity === 'Critical' || p.severity === 'Warning' || p.severity === 'Error'));
// ์ ๋ ฌ: Critical ์ฐ์ , ๊ทธ ๋ค์ Warning/Error
problems.sort((a, b) => {
const severityScore = (severity) => {
if (severity === 'Critical') return 2;
if (severity === 'Warning' || severity === 'Error') return 1;
return 0;
};
return severityScore(b.severity) - severityScore(a.severity);
});
} catch (error) {
console.error("๋ฌธ์ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ ์ค๋ฅ:", error);
alert('๋ฌธ์ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.');
return;
}
// ๋ฌธ์ ๊ฐ ์๋ ๊ฒฝ์ฐ
if (problems.length === 0) {
alert('ํ์ฌ ๊ฐ์ง๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.');
return;
}
try {
// ๊ธฐ์กด ๋ชจ๋ฌ์ด ์์ผ๋ฉด ์ ๊ฑฐ
const existingModal = document.getElementById('allProblemsModal');
if (existingModal) {
existingModal.remove();
}
// ๋ชจ๋ ๋ฌธ์ ๋ฅผ ํ์ํ๋ ๋ชจ๋ฌ ์์ฑ
const modalHTML = `
<div class="modal fade" id="allProblemsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-exclamation-triangle me-2 text-danger"></i> ์ ์ฒด ์๋ฒ ๋ฌธ์ ๋ชฉ๋ก
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="problems-count mb-3">
์ด <span class="fw-bold">${problems.length}</span>๊ฐ์ ๋ฌธ์ ๊ฐ ๊ฐ์ง๋์์ต๋๋ค.
</div>
<div class="alert alert-info mb-3">
<i class="fas fa-info-circle me-2"></i>
๊ฐ ๋ฌธ์ ๋ฅผ ํด๋ฆญํ๋ฉด ํด๋น ์๋ฒ์ ์์ธ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
</div>
<ul class="list-group all-problems-list">
${problems.map((problem, idx) => `
<li class="list-group-item list-group-item-action problem-item severity-${problem.severity.toLowerCase()}" data-index="${idx}">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1 problem-description">${problem.description}</h6>
<small class="text-muted">${problem.serverHostname || '์ ์ ์๋ ์๋ฒ'}</small>
</div>
<p class="mb-1 problem-solution">${problem.solution || '์ ์๋ ํด๊ฒฐ์ฑ
์์'}</p>
<small class="text-muted">์ฌ๊ฐ๋: <span class="fw-bold problem-severity-text">${problem.severity}</span></small>
<div class="problem-hint-icon">
<i class="fas fa-search-plus"></i>
</div>
</li>
`).join('')}
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="downloadModalReport">
<i class="bi bi-download"></i> ๋ณด๊ณ ์ ๋ค์ด๋ก๋
</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">๋ซ๊ธฐ</button>
</div>
</div>
</div>
</div>
`;
// ๋ชจ๋ฌ์ ํ์ด์ง์ ์ถ๊ฐ
document.body.insertAdjacentHTML('beforeend', modalHTML);
// ๋ชจ๋ฌ ์์ ๊ฐ์ ธ์ค๊ธฐ
const modalElement = document.getElementById('allProblemsModal');
if (!modalElement) {
console.error("๋ชจ๋ฌ ์์ ์์ฑ ์คํจ");
return;
}
// jQuery๋ก ๋ชจ๋ฌ ํ์ ์๋ (๋ถํธ์คํธ๋ฉ ์์กด์ฑ ์ค์ด๊ธฐ)
if (window.jQuery && window.jQuery.fn.modal) {
const $modal = window.jQuery(modalElement);
$modal.modal('show');
// ๋ฌธ์ ํญ๋ชฉ์ ํด๋ฆญ ์ด๋ฒคํธ ์ถ๊ฐ
window.jQuery('.problem-item').on('click', function() {
const index = parseInt(window.jQuery(this).data('index'));
const serverHostname = problems[index].serverHostname;
if (!serverHostname) return;
const server = this.serverData.find(s => s.hostname === serverHostname);
if (server) {
$modal.modal('hide');
setTimeout(() => {
this.showServerDetail(server);
}, 500);
}
}.bind(this));
// ๋ณด๊ณ ์ ๋ค์ด๋ก๋ ๋ฒํผ ์ด๋ฒคํธ
window.jQuery('#downloadModalReport').on('click', this.downloadErrorReport.bind(this));
return; // jQuery๋ก ์ฑ๊ณต์ ์ผ๋ก ํ์ํ์ผ๋ฉด ์ฌ๊ธฐ์ ์ข
๋ฃ
}
// ๋ถํธ์คํธ๋ฉ ๋ชจ๋ฌ ์ธ์คํด์ค ์์ฑ ๋ฐ ํ์
if (typeof bootstrap !== 'undefined' && typeof bootstrap.Modal === 'function') {
// ๋ถํธ์คํธ๋ฉ 5+
const bsModal = new bootstrap.Modal(modalElement);
bsModal.show();
// ๋ฌธ์ ํญ๋ชฉ ํด๋ฆญ ์ด๋ฒคํธ ์ถ๊ฐ
const problemItems = modalElement.querySelectorAll('.problem-item');
problemItems.forEach(item => {
// ์ ๋๋ฉ์ด์
๋๋ ์ด ์ค์
const index = parseInt(item.dataset.index);
item.style.setProperty('--item-index', index);
item.addEventListener('click', () => {
// ํด๋น ์๋ฒ ๋ชจ๋ฌ ํ์
const serverHostname = problems[index].serverHostname;
if (!serverHostname) return;
const server = this.serverData.find(s => s.hostname === serverHostname);
if (server) {
bsModal.hide(); // ํ์ฌ ๋ชจ๋ฌ ๋ซ๊ธฐ
setTimeout(() => {
this.showServerDetail(server); // ์๋ฒ ์์ธ ๋ชจ๋ฌ ํ์
}, 500);
}
});
});
} else {
// ์์ JavaScript๋ก ๋ชจ๋ฌ ํ์ (fallback)
modalElement.style.display = 'block';
modalElement.classList.add('show');
document.body.classList.add('modal-open');
// ๋ฐฐ๊ฒฝ ์์ ์ถ๊ฐ
const backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop fade show';
document.body.appendChild(backdrop);
// ๋ซ๊ธฐ ๋ฒํผ์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ถ๊ฐ
const closeButtons = modalElement.querySelectorAll('[data-bs-dismiss="modal"]');
closeButtons.forEach(btn => {
btn.addEventListener('click', () => {
modalElement.style.display = 'none';
modalElement.classList.remove('show');
document.body.classList.remove('modal-open');
document.querySelector('.modal-backdrop')?.remove();
});
});
// ๋ฌธ์ ํญ๋ชฉ ํด๋ฆญ ์ด๋ฒคํธ ์ถ๊ฐ
const problemItems = modalElement.querySelectorAll('.problem-item');
problemItems.forEach(item => {
const index = parseInt(item.dataset.index);
item.style.setProperty('--item-index', index);
item.addEventListener('click', () => {
const serverHostname = problems[index].serverHostname;
if (!serverHostname) return;
const server = this.serverData.find(s => s.hostname === serverHostname);
if (server) {
// ๋ชจ๋ฌ ์๋ ๋ซ๊ธฐ
modalElement.style.display = 'none';
modalElement.classList.remove('show');
document.body.classList.remove('modal-open');
document.querySelector('.modal-backdrop')?.remove();
setTimeout(() => {
this.showServerDetail(server);
}, 500);
}
});
});
}
// ๋ณด๊ณ ์ ๋ค์ด๋ก๋ ๋ฒํผ ์ด๋ฒคํธ
const downloadBtn = document.getElementById('downloadModalReport');
if (downloadBtn) {
downloadBtn.addEventListener('click', () => {
this.downloadErrorReport();
});
}
} catch (error) {
console.error('๋ชจ๋ฌ ํ์ ์ค ์ค๋ฅ ๋ฐ์:', error);
alert('๋ฌธ์ ๋ชฉ๋ก์ ํ์ํ ์ ์์ต๋๋ค.');
}
}
// ๋ฌธ์ ์์ธ ๋ณด๊ณ ์ ๋ชจ๋ฌ ํ์ (์๋ก ์ถ๊ฐ)
showProblemDetailModal(problem) {
if (!problem) {
console.error("๋ฌธ์ ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.");
return;
}
// ์๋ฒ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
const server = this.serverData.find(s => s.hostname === problem.serverHostname);
try {
// ๊ธฐ์กด ๋ชจ๋ฌ ์ ๊ฑฐ (์ค๋ณต ์์ฑ ๋ฐฉ์ง)
const existingModal = document.getElementById('problemDetailModal');
if (existingModal) {
existingModal.remove();
}
// ๋ชจ๋ฌ HTML ์์ฑ ๋ฐ ์ถ๊ฐ
const modalHTML = `
<div class="modal fade" id="problemDetailModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-exclamation-triangle me-2 text-danger"></i> <span id="problemTitle">๋ฌธ์ ์์ธ ๋ณด๊ณ ์</span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row mb-3">
<div class="col-12">
<div class="alert alert-danger">
<h6 class="alert-heading">๋ฌธ์ ์ค๋ช
</h6>
<p id="problemDescription" class="mb-0"></p>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="card">
<div class="card-header bg-light">
<h6 class="mb-0">์๋ฒ ์ ๋ณด</h6>
</div>
<div class="card-body">
<table class="table table-sm">
<tbody id="problemServerInfoTable">
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header bg-light">
<h6 class="mb-0">๋ฌธ์ ์์ธ</h6>
</div>
<div class="card-body">
<ul id="problemCausesList" class="mb-0"></ul>
</div>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<div class="card">
<div class="card-header bg-light">
<h6 class="mb-0">ํด๊ฒฐ ๋ฐฉ๋ฒ</h6>
</div>
<div class="card-body">
<ul id="problemSolutionsList" class="mb-0"></ul>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header bg-light">
<h6 class="mb-0">์์ธ ๋ฉํธ๋ฆญ ์ ๋ณด</h6>
</div>
<div class="card-body">
<div id="problemMetricsInfo"></div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="downloadReportBtn">
<i class="bi bi-download me-1"></i> ๋ณด๊ณ ์ ๋ค์ด๋ก๋ (.txt)
</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">๋ซ๊ธฐ</button>
</div>
</div>
</div>
</div>`;
// ๋ชจ๋ฌ์ ํ์ด์ง์ ์ถ๊ฐ
document.body.insertAdjacentHTML('beforeend', modalHTML);
// ๋ชจ๋ฌ ์์๋ฅผ ๋ค์ ๊ฐ์ ธ์ด
const problemModal = document.getElementById('problemDetailModal');
if (!problemModal) {
console.error("๋ชจ๋ฌ ์์ ์์ฑ ์คํจ");
return;
}
// ๋ฌธ์ ์์ธ ์ ๋ณด ํ์
const problemTitle = document.getElementById('problemTitle');
const problemDescription = document.getElementById('problemDescription');
const serverInfoTable = document.getElementById('problemServerInfoTable');
const causesList = document.getElementById('problemCausesList');
const solutionsList = document.getElementById('problemSolutionsList');
const metricsInfo = document.getElementById('problemMetricsInfo');
// ์์ ํ๊ฒ ๋ด์ฉ ์
๋ฐ์ดํธ
if (problemTitle) problemTitle.textContent = `${problem.severity} ๋ฌธ์ ๋ณด๊ณ ์: ${server ? server.hostname : '์ ์ ์๋ ์๋ฒ'}`;
if (problemDescription) problemDescription.textContent = problem.description;
// ์๋ฒ ์ ๋ณด ํ
์ด๋ธ ์
๋ฐ์ดํธ
if (serverInfoTable && server) {
serverInfoTable.innerHTML = `
<tr>
<th>ํธ์คํธ๋ช
</th>
<td>${server.hostname}</td>
</tr>
<tr>
<th>IP ์ฃผ์</th>
<td>${server.ip}</td>
</tr>
<tr>
<th>OS</th>
<td>${server.os}</td>
</tr>
<tr>
<th>์ํ</th>
<td><span class="badge bg-${this.getStatusColorClass(this.getServerStatus(server))}">${this.getStatusLabel(this.getServerStatus(server))}</span></td>
</tr>
<tr>
<th>CPU ์ฌ์ฉ๋ฅ </th>
<td>${server.cpu_usage}%</td>
</tr>
<tr>
<th>๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ </th>
<td>${server.memory_usage_percent}%</td>
</tr>
<tr>
<th>๋์คํฌ ์ฌ์ฉ๋ฅ </th>
<td>${server.disk && server.disk.length > 0 ? server.disk[0].disk_usage_percent + '%' : 'N/A'}</td>
</tr>
`;
}
// ์์ธ ๋ฐ ํด๊ฒฐ ๋ฐฉ๋ฒ ๋ชฉ๋ก ์ฑ์ฐ๊ธฐ
if (causesList) {
causesList.innerHTML = '';
const causes = problem.causes || ["์์ธ ๋ฐ์ดํฐ ์์"];
causes.forEach(cause => {
const li = document.createElement('li');
li.textContent = cause;
causesList.appendChild(li);
});
}
if (solutionsList) {
solutionsList.innerHTML = '';
const solutions = problem.solutions || [problem.solution || "ํด๊ฒฐ ๋ฐฉ๋ฒ ๋ฐ์ดํฐ ์์"];
solutions.forEach(solution => {
const li = document.createElement('li');
li.textContent = solution;
solutionsList.appendChild(li);
});
}
// ์์ธ ๋ฉํธ๋ฆญ ์ ๋ณด
if (metricsInfo && server) {
metricsInfo.innerHTML = `
<div class="row">
<div class="col-md-6">
<h6 class="mt-2 mb-3">๋ฆฌ์์ค ์ฌ์ฉ๋</h6>
<div class="progress mb-2" style="height: 25px;">
<div class="progress-bar ${this.getResourceStatus(server.cpu_usage, 'cpu') !== 'normal' ? 'bg-danger' : 'bg-success'}"
role="progressbar" style="width: ${server.cpu_usage}%">
CPU ${server.cpu_usage}%
</div>
</div>
<div class="progress mb-2" style="height: 25px;">
<div class="progress-bar ${this.getResourceStatus(server.memory_usage_percent, 'memory') !== 'normal' ? 'bg-danger' : 'bg-success'}"
role="progressbar" style="width: ${server.memory_usage_percent}%">
๋ฉ๋ชจ๋ฆฌ ${server.memory_usage_percent}%
</div>
</div>
<div class="progress mb-2" style="height: 25px;">
<div class="progress-bar ${server.disk && server.disk.length > 0 && this.getResourceStatus(server.disk[0].disk_usage_percent, 'disk') !== 'normal' ? 'bg-danger' : 'bg-success'}"
role="progressbar" style="width: ${server.disk && server.disk.length > 0 ? server.disk[0].disk_usage_percent : 0}%">
๋์คํฌ ${server.disk && server.disk.length > 0 ? server.disk[0].disk_usage_percent : 0}%
</div>
</div>
</div>
<div class="col-md-6">
<h6 class="mt-2 mb-3">๋คํธ์ํฌ ์ ๋ณด</h6>
<p><strong>์์ :</strong> ${this.formatBytes(server.net.rx_bytes)}</p>
<p><strong>์ก์ :</strong> ${this.formatBytes(server.net.tx_bytes)}</p>
<p><strong>์ค๋ฅ:</strong> RX: ${server.net.rx_errors}, TX: ${server.net.tx_errors}</p>
</div>
</div>
`;
}
// ๋ณด๊ณ ์ ๋ค์ด๋ก๋ ๋ฒํผ ์ด๋ฒคํธ ๋ฆฌ์ค๋
const downloadBtn = document.getElementById('downloadReportBtn');
if (downloadBtn) {
// ์ด์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ ๊ฑฐ
downloadBtn.replaceWith(downloadBtn.cloneNode(true));
// ์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ถ๊ฐ
document.getElementById('downloadReportBtn').addEventListener('click', () => {
this.downloadProblemReport(problem, server);
});
}
// jQuery๋ก ๋ชจ๋ฌ ํ์ ์๋ (๋ถํธ์คํธ๋ฉ ์์กด์ฑ ์ค์ด๊ธฐ)
if (window.jQuery && window.jQuery.fn.modal) {
window.jQuery(problemModal).modal('show');
return; // jQuery๋ก ์ฑ๊ณต์ ์ผ๋ก ํ์ํ์ผ๋ฉด ์ฌ๊ธฐ์ ์ข
๋ฃ
}
// ๋ถํธ์คํธ๋ฉ ๋ชจ๋ฌ ์ธ์คํด์ค ์์ฑ ๋ฐ ํ์
if (typeof bootstrap !== 'undefined' && typeof bootstrap.Modal === 'function') {
// ๋ถํธ์คํธ๋ฉ 5+
const bsModal = new bootstrap.Modal(problemModal);
bsModal.show();
} else {
// ์์ JavaScript๋ก ๋ชจ๋ฌ ํ์ (fallback)
problemModal.style.display = 'block';
problemModal.classList.add('show');
document.body.classList.add('modal-open');
// ๋ฐฐ๊ฒฝ ์์ ์ถ๊ฐ
const backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop fade show';
document.body.appendChild(backdrop);
// ๋ซ๊ธฐ ๋ฒํผ์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ถ๊ฐ
const closeButtons = problemModal.querySelectorAll('[data-bs-dismiss="modal"]');
closeButtons.forEach(btn => {
btn.addEventListener('click', () => {
problemModal.style.display = 'none';
problemModal.classList.remove('show');
document.body.classList.remove('modal-open');
document.querySelector('.modal-backdrop')?.remove();
});
});
}
} catch (error) {
console.error('๋ชจ๋ฌ ํ์ ์ค ์ค๋ฅ ๋ฐ์:', error);
alert('๋ฌธ์ ์์ธ ์ ๋ณด๋ฅผ ํ์ํ ์ ์์ต๋๋ค.');
}
}
// ๋ฌธ์ ๋ณด๊ณ ์ ๋ค์ด๋ก๋ (์๋ก ์ถ๊ฐ)
downloadProblemReport(problem, server) {
if (!problem || !server) return;
const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
const filename = `${server.hostname}_${problem.severity}_report_${timestamp}.txt`;
let reportContent = `=========================================\n`;
reportContent += ` OpenManager AI ์๋ ์ฅ์ ๋ณด๊ณ ์\n`;
reportContent += `=========================================\n\n`;
reportContent += `[์์ฑ ์ผ์]: ${new Date().toLocaleString()}\n\n`;
reportContent += `[๋ฌธ์ ์์ฝ]\n`;
reportContent += `์ฌ๊ฐ๋: ${problem.severity}\n`;
reportContent += `์ค๋ช
: ${problem.description}\n\n`;
reportContent += `[์๋ฒ ์ ๋ณด]\n`;
reportContent += `ํธ์คํธ๋ช
: ${server.hostname}\n`;
reportContent += `IP: ${server.ip}\n`;
reportContent += `OS: ${server.os}\n`;
reportContent += `์ํ: ${this.getStatusLabel(this.getServerStatus(server))}\n\n`;
reportContent += `[๋ฆฌ์์ค ํํฉ]\n`;
reportContent += `CPU ์ฌ์ฉ๋ฅ : ${server.cpu_usage}%\n`;
reportContent += `๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ : ${server.memory_usage_percent}%\n`;
reportContent += `๋์คํฌ ์ฌ์ฉ๋ฅ : ${server.disk && server.disk.length > 0 ? server.disk[0].disk_usage_percent + '%' : 'N/A'}\n`;
reportContent += `๋คํธ์ํฌ ์์ : ${this.formatBytes(server.net.rx_bytes)}\n`;
reportContent += `๋คํธ์ํฌ ์ก์ : ${this.formatBytes(server.net.tx_bytes)}\n`;
reportContent += `๋คํธ์ํฌ ์ค๋ฅ (RX/TX): ${server.net.rx_errors}/${server.net.tx_errors}\n\n`;
if (problem.causes && problem.causes.length) {
reportContent += `[์ถ์ ์์ธ]\n`;
problem.causes.forEach((cause, index) => {
reportContent += `${index + 1}. ${cause}\n`;
});
reportContent += `\n`;
}
reportContent += `[ํด๊ฒฐ ๋ฐฉ๋ฒ]\n`;
const solutions = problem.solutions || [problem.solution || "ํด๊ฒฐ ๋ฐฉ๋ฒ ๋ฐ์ดํฐ ์์"];
solutions.forEach((solution, index) => {
reportContent += `${index + 1}. ${solution}\n`;
});
reportContent += `\n`;
if (server.errors && server.errors.length) {
reportContent += `[์ค๋ฅ ๋ก๊ทธ]\n`;
server.errors.forEach((error, index) => {
reportContent += `${index + 1}. ${error}\n`;
});
}
reportContent += `\n=========================================\n`;
reportContent += `์ด ๋ณด๊ณ ์๋ OpenManager AI์ ์ํด ์๋ ์์ฑ๋์์ต๋๋ค.\n`;
reportContent += `๋ฌธ์: support@openmanager.ai\n`;
// ํ
์คํธ ํ์ผ๋ก ๋ค์ด๋ก๋
const blob = new Blob([reportContent], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// ์ ์ฒด ๋ฌธ์ ๋ณด๊ณ ์ ๋ค์ด๋ก๋ (์๋ก ์ถ๊ฐ)
downloadAllProblemsReport() {
if (!this.problemsData || this.problemsData.length === 0) {
alert('๋ค์ด๋ก๋ํ ๋ฌธ์ ๋ณด๊ณ ์๊ฐ ์์ต๋๋ค.');
return;
}
const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
const filename = `OpenManager_all_problems_report_${timestamp}.txt`;
let reportContent = `=========================================\n`;
reportContent += ` OpenManager AI ์๋ ์ฅ์ ๋ณด๊ณ ์ (์ ์ฒด)\n`;
reportContent += `=========================================\n\n`;
reportContent += `[์์ฑ ์ผ์]: ${new Date().toLocaleString()}\n`;
reportContent += `[์ด ๋ฌธ์ ์]: ${this.problemsData.length}๊ฐ\n\n`;
this.problemsData.forEach((problem, index) => {
const server = this.serverData.find(s => s.hostname === problem.serverHostname);
if (!server) return;
reportContent += `\n=========================================\n`;
reportContent += `๋ฌธ์ #${index + 1}: ${problem.severity} - ${server.hostname}\n`;
reportContent += `=========================================\n\n`;
reportContent += `[๋ฌธ์ ์์ฝ]\n`;
reportContent += `์ฌ๊ฐ๋: ${problem.severity}\n`;
reportContent += `์ค๋ช
: ${problem.description}\n\n`;
reportContent += `[์๋ฒ ์ ๋ณด]\n`;
reportContent += `ํธ์คํธ๋ช
: ${server.hostname}\n`;
reportContent += `IP: ${server.ip}\n`;
reportContent += `OS: ${server.os}\n`;
reportContent += `์ํ: ${this.getStatusLabel(this.getServerStatus(server))}\n\n`;
reportContent += `[๋ฆฌ์์ค ํํฉ]\n`;
reportContent += `CPU ์ฌ์ฉ๋ฅ : ${server.cpu_usage}%\n`;
reportContent += `๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ : ${server.memory_usage_percent}%\n`;
reportContent += `๋์คํฌ ์ฌ์ฉ๋ฅ : ${server.disk && server.disk.length > 0 ? server.disk[0].disk_usage_percent + '%' : 'N/A'}\n`;
reportContent += `๋คํธ์ํฌ ์์ : ${this.formatBytes(server.net.rx_bytes)}\n`;
reportContent += `๋คํธ์ํฌ ์ก์ : ${this.formatBytes(server.net.tx_bytes)}\n`;
reportContent += `๋คํธ์ํฌ ์ค๋ฅ (RX/TX): ${server.net.rx_errors}/${server.net.tx_errors}\n\n`;
if (problem.causes && problem.causes.length) {
reportContent += `[์ถ์ ์์ธ]\n`;
problem.causes.forEach((cause, causeIndex) => {
reportContent += `${causeIndex + 1}. ${cause}\n`;
});
reportContent += `\n`;
}
reportContent += `[ํด๊ฒฐ ๋ฐฉ๋ฒ]\n`;
const solutions = problem.solutions || [problem.solution || "ํด๊ฒฐ ๋ฐฉ๋ฒ ๋ฐ์ดํฐ ์์"];
solutions.forEach((solution, solutionIndex) => {
reportContent += `${solutionIndex + 1}. ${solution}\n`;
});
reportContent += `\n`;
if (server.errors && server.errors.length) {
reportContent += `[์ค๋ฅ ๋ก๊ทธ]\n`;
server.errors.forEach((error, errorIndex) => {
reportContent += `${errorIndex + 1}. ${error}\n`;
});
reportContent += `\n`;
}
});
reportContent += `\n=========================================\n`;
reportContent += `์ด ๋ณด๊ณ ์๋ OpenManager AI์ ์ํด ์๋ ์์ฑ๋์์ต๋๋ค.\n`;
reportContent += `์์ฑ ์ผ์: ${new Date().toLocaleString()}\n`;
reportContent += `๋ฌธ์: support@openmanager.ai\n`;
// ํ
์คํธ ํ์ผ๋ก ๋ค์ด๋ก๋
const blob = new Blob([reportContent], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
}
// ๋ฐ์ดํฐ ํ๋ก์ธ์ ์ธ์คํด์ค ์์ฑ
window.addEventListener('DOMContentLoaded', () => {
if (!window.aiProcessor) window.aiProcessor = new AIProcessor();
new MCPQueryManager();
});
// MCP ์ง๋ฌธ/์๋ต ํ์ฅ ๊ธฐ๋ฅ ์ถ๊ฐ
class MCPQueryManager {
constructor() {
this.historyKey = 'mcpQueryHistory';
this.history = this.loadHistory();
this.context = null;
this.mcpUrl = 'https://openmanager-vibe-v4.onrender.com/query'; // ์ค์ MCP ์๋ฒ ์ฃผ์๋ก ๊ต์ฒด
this.init();
}
async init() {
await this.loadContext();
this.initUI();
this.renderHistory();
}
async loadContext() {
try {
const res = await fetch('/public/context/server-status.txt');
this.context = await res.text();
} catch (e) {
this.context = '';
}
}
initUI() {
const input = document.getElementById('queryInput');
const button = document.getElementById('ai-query-submit');
const result = document.getElementById('queryResultContent');
const resultBox = document.getElementById('queryResult');
const loading = document.getElementById('queryLoading');
const closeBtn = document.getElementById('closeQueryResult');
const exampleBtns = document.querySelectorAll('.example-query-btn');
if (input && button) {
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.handleQuery(input, result, resultBox, loading);
});
button.addEventListener('click', () => this.handleQuery(input, result, resultBox, loading));
}
if (closeBtn && resultBox) {
closeBtn.addEventListener('click', () => {
resultBox.classList.remove('active');
resultBox.style.display = 'none';
});
}
if (exampleBtns) {
exampleBtns.forEach(btn => {
btn.addEventListener('click', () => {
input.value = btn.textContent;
this.handleQuery(input, result, resultBox, loading);
});
});
}
}
async handleQuery(input, result, resultBox, loading) {
const query = input.value?.trim();
if (!query || query === 'undefined') return; // ๋น ์
๋ ฅ ๋ฐฉ์ง
loading.style.display = 'block';
resultBox.classList.remove('active');
resultBox.style.display = 'none';
try {
// processQuery ๋์ aiProcessor ์ธ์คํด์ค๋ฅผ ํตํด ํธ์ถ
const answer = window.aiProcessor ?
await window.aiProcessor.processQuery(query) :
"AI ์ฒ๋ฆฌ ์ปดํฌ๋ํธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.";
if (!answer || answer === 'undefined' || answer.trim() === '') {
result.innerHTML = '์ ์ ํ ๋ต๋ณ์ ์ฐพ์ง ๋ชปํ์ต๋๋ค.';
} else {
result.innerHTML = window.marked ? window.marked.parse(answer) : answer;
}
resultBox.classList.add('active');
resultBox.style.display = 'block';
this.addHistory(query, answer);
this.renderHistory();
} catch (e) {
result.innerHTML = '์ง๋ฌธ ์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.';
resultBox.classList.add('active');
resultBox.style.display = 'block';
} finally {
loading.style.display = 'none';
input.value = '';
}
}
addHistory(query, answer) {
this.history.unshift({ query, answer, ts: new Date().toISOString() });
if (this.history.length > 30) this.history.pop();
localStorage.setItem(this.historyKey, JSON.stringify(this.history));
}
loadHistory() {
try {
return JSON.parse(localStorage.getItem(this.historyKey)) || [];
} catch {
return [];
}
}
renderHistory() {
const histBox = document.getElementById('queryHistory');
if (!histBox) return;
histBox.innerHTML = this.history.map(item => `
<div class="query-item">
<div class="query-question"><i class="fas fa-user"></i> <span>${item.query}</span></div>
<div class="query-answer markdown-content">${window.marked ? window.marked.parse(item.answer) : item.answer}</div>
<div class="query-timestamp">${new Date(item.ts).toLocaleString()}</div>
</div>
`).join('');
}
}
window.addEventListener('DOMContentLoaded', () => {
new MCPQueryManager();
});