Skip to main content
Glama

OpenManager Vibe V4 MCP Server

by skyasu2
ChartManager.js13.6 kB
import EventBus from '../services/EventBus.js'; export default class ChartManager { constructor() { // 차트 객체 저장소 this.charts = {}; // 차트 렌더링 대상 요소 this.elements = { summaryCharts: document.getElementById('summary-charts'), serverDetailCharts: document.getElementById('server-detail-charts'), aiVisualization: document.getElementById('visualization') }; // 이벤트 구독 this.subscribeToEvents(); // 차트 라이브러리 초기화 this.initChartLibrary(); } subscribeToEvents() { // 서버 데이터 업데이트 이벤트 EventBus.subscribe('servers:data-updated', this.updateServerCharts.bind(this)); // AI 응답 이벤트 EventBus.subscribe('ai:response-received', this.handleAIVisualization.bind(this)); // 서버 선택 이벤트 EventBus.subscribe('server:selected', this.showServerDetailCharts.bind(this)); } initChartLibrary() { // Chart.js 또는 다른 차트 라이브러리 설정 // 예: 글로벌 옵션 설정, 테마 등 if (window.Chart) { Chart.defaults.font.family = "'Noto Sans', 'Helvetica', sans-serif"; Chart.defaults.color = '#555'; Chart.defaults.responsive = true; Chart.defaults.maintainAspectRatio = false; } } updateServerCharts(serversData) { // 서버 요약 차트 생성/업데이트 this.renderStatusDistributionChart(serversData); this.renderResourceUtilizationChart(serversData); } handleAIVisualization(response) { const visualizationType = response.visualization_type; if (!visualizationType || !this.elements.aiVisualization) return; // 이전 차트 정리 if (this.charts.aiChart) { this.charts.aiChart.destroy(); } // 시각화 타입에 따른 차트 렌더링 switch (visualizationType) { case 'cpu_chart': this.renderCPUChart(response.related_servers, response.data); break; case 'memory_chart': this.renderMemoryChart(response.related_servers, response.data); break; case 'disk_chart': this.renderDiskChart(response.related_servers, response.data); break; case 'status_summary': this.renderStatusSummaryChart(response.data); break; case 'incident_timeline': this.renderIncidentTimeline(response.data); break; case 'recommendation': // 권장사항은 특별한 시각화 없음 this.elements.aiVisualization.innerHTML = ''; break; default: this.elements.aiVisualization.innerHTML = '<div class="no-visualization">No visualization available</div>'; } } showServerDetailCharts(server) { if (!this.elements.serverDetailCharts) return; // 이전 차트 정리 if (this.charts.serverDetailChart) { this.charts.serverDetailChart.destroy(); } // 서버 상세 컨테이너 준비 this.elements.serverDetailCharts.innerHTML = ` <div class="chart-container"> <canvas id="server-history-chart"></canvas> </div> `; // 서버 메트릭 히스토리 차트 생성 this.renderServerHistoryChart(server); } // 상태 분포 차트 (파이/도넛 차트) renderStatusDistributionChart(serversData) { if (!this.elements.summaryCharts) return; // 서버 상태별 카운트 계산 const statusCount = { normal: 0, warning: 0, critical: 0 }; serversData.forEach(server => { statusCount[server.status] = (statusCount[server.status] || 0) + 1; }); // 차트 준비 if (!document.getElementById('status-chart')) { const chartContainer = document.createElement('div'); chartContainer.className = 'chart-container'; chartContainer.innerHTML = '<canvas id="status-chart"></canvas>'; this.elements.summaryCharts.appendChild(chartContainer); } // 차트 생성/업데이트 const ctx = document.getElementById('status-chart').getContext('2d'); if (this.charts.statusChart) { this.charts.statusChart.data.datasets[0].data = [ statusCount.normal, statusCount.warning, statusCount.critical ]; this.charts.statusChart.update(); } else { this.charts.statusChart = new Chart(ctx, { type: 'doughnut', data: { labels: ['Normal', 'Warning', 'Critical'], datasets: [{ data: [statusCount.normal, statusCount.warning, statusCount.critical], backgroundColor: ['#4caf50', '#ff9800', '#f44336'], borderWidth: 1 }] }, options: { plugins: { title: { display: true, text: 'Server Status Distribution' }, legend: { position: 'bottom' } } } }); } } // 리소스 사용량 차트 (바 차트) renderResourceUtilizationChart(serversData) { if (!this.elements.summaryCharts) return; // 평균 리소스 사용량 계산 let totalCPU = 0, totalMemory = 0, totalDisk = 0; serversData.forEach(server => { totalCPU += server.cpu || 0; totalMemory += server.memory || 0; totalDisk += server.disk || 0; }); const avgCPU = serversData.length ? (totalCPU / serversData.length).toFixed(1) : 0; const avgMemory = serversData.length ? (totalMemory / serversData.length).toFixed(1) : 0; const avgDisk = serversData.length ? (totalDisk / serversData.length).toFixed(1) : 0; // 차트 준비 if (!document.getElementById('resource-chart')) { const chartContainer = document.createElement('div'); chartContainer.className = 'chart-container'; chartContainer.innerHTML = '<canvas id="resource-chart"></canvas>'; this.elements.summaryCharts.appendChild(chartContainer); } // 차트 생성/업데이트 const ctx = document.getElementById('resource-chart').getContext('2d'); if (this.charts.resourceChart) { this.charts.resourceChart.data.datasets[0].data = [avgCPU, avgMemory, avgDisk]; this.charts.resourceChart.update(); } else { this.charts.resourceChart = new Chart(ctx, { type: 'bar', data: { labels: ['CPU', 'Memory', 'Disk'], datasets: [{ label: 'Average Usage (%)', data: [avgCPU, avgMemory, avgDisk], backgroundColor: ['rgba(54, 162, 235, 0.7)', 'rgba(75, 192, 192, 0.7)', 'rgba(255, 159, 64, 0.7)'], borderColor: ['rgb(54, 162, 235)', 'rgb(75, 192, 192)', 'rgb(255, 159, 64)'], borderWidth: 1 }] }, options: { plugins: { title: { display: true, text: 'Average Resource Utilization' } }, scales: { y: { beginAtZero: true, max: 100, ticks: { callback: function(value) { return value + '%'; } } } } } }); } } // 서버 히스토리 차트 (라인 차트) renderServerHistoryChart(server) { // 실제 구현에서는 이 서버의 시계열 데이터를 가져와야 함 // 현재는 더미 데이터 사용 const labels = Array.from({length: 24}, (_, i) => `${i}:00`); const cpuData = Array.from({length: 24}, () => Math.floor(Math.random() * 40) + server.cpu - 20); const memoryData = Array.from({length: 24}, () => Math.floor(Math.random() * 40) + server.memory - 20); const diskData = Array.from({length: 24}, () => Math.floor(Math.random() * 10) + server.disk - 5); const ctx = document.getElementById('server-history-chart').getContext('2d'); this.charts.serverDetailChart = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [ { label: 'CPU Usage', data: cpuData, borderColor: 'rgb(54, 162, 235)', backgroundColor: 'rgba(54, 162, 235, 0.1)', tension: 0.4, fill: true }, { label: 'Memory Usage', data: memoryData, borderColor: 'rgb(75, 192, 192)', backgroundColor: 'rgba(75, 192, 192, 0.1)', tension: 0.4, fill: true }, { label: 'Disk Usage', data: diskData, borderColor: 'rgb(255, 159, 64)', backgroundColor: 'rgba(255, 159, 64, 0.1)', tension: 0.4, fill: true } ] }, options: { plugins: { title: { display: true, text: `${server.hostname} - 24-Hour Resource History` }, tooltip: { mode: 'index', intersect: false } }, scales: { y: { min: 0, max: 100, ticks: { callback: function(value) { return value + '%'; } } } } } }); } // AI 응답용 특수 차트들 renderCPUChart(servers, data) { this.renderMetricBarChart('CPU Usage', servers, 'cpu', '#36a2eb'); } renderMemoryChart(servers, data) { this.renderMetricBarChart('Memory Usage', servers, 'memory', '#4bc0c0'); } renderDiskChart(servers, data) { this.renderMetricBarChart('Disk Usage', servers, 'disk', '#ff9f40'); } renderMetricBarChart(title, servers, metricKey, color) { if (!servers || !servers.length) return; const serverData = window.dashboard.servers.filter(s => servers.includes(s.hostname) || servers.includes(s.id) ); if (!serverData.length) return; const labels = serverData.map(s => s.hostname); const data = serverData.map(s => s[metricKey] || 0); const ctx = this.elements.aiVisualization; ctx.innerHTML = '<canvas id="ai-metric-chart"></canvas>'; const chartCanvas = document.getElementById('ai-metric-chart'); this.charts.aiChart = new Chart(chartCanvas.getContext('2d'), { type: 'bar', data: { labels: labels, datasets: [{ label: title, data: data, backgroundColor: color, borderColor: color, borderWidth: 1 }] }, options: { indexAxis: 'y', plugins: { title: { display: true, text: title } }, scales: { x: { beginAtZero: true, max: 100, ticks: { callback: function(value) { return value + '%'; } } } } } }); } renderStatusSummaryChart(data) { // statusCount와 같은 데이터 예상 if (!data) { // 전체 서버 상태 카운트 계산 const servers = window.dashboard?.servers || []; data = { total: servers.length, normal: servers.filter(s => s.status === 'normal').length, warning: servers.filter(s => s.status === 'warning').length, critical: servers.filter(s => s.status === 'critical').length }; } const ctx = this.elements.aiVisualization; ctx.innerHTML = '<canvas id="ai-status-chart"></canvas>'; const chartCanvas = document.getElementById('ai-status-chart'); this.charts.aiChart = new Chart(chartCanvas.getContext('2d'), { type: 'pie', data: { labels: ['Normal', 'Warning', 'Critical'], datasets: [{ data: [data.normal, data.warning, data.critical], backgroundColor: ['#4caf50', '#ff9800', '#f44336'], borderWidth: 1 }] }, options: { plugins: { title: { display: true, text: 'Server Status Summary' }, legend: { position: 'right' } } } }); } renderIncidentTimeline(data) { // 이 경우 특별한 타임라인 시각화 필요 // 현재는 간단한 HTML로 대체 this.elements.aiVisualization.innerHTML = ` <div class="incident-timeline"> <h3>Recent Incidents Timeline</h3> <div class="timeline"> ${this.generateIncidentTimelineHTML(data)} </div> </div> `; } generateIncidentTimelineHTML(data) { // 데이터가 없는 경우 샘플 데이터 사용 const incidents = data || [ { server: 'web-03', time: '1 hour ago', message: 'High CPU usage detected', severity: 'warning' }, { server: 'db-01', time: '3 hours ago', message: 'Database connection failed', severity: 'critical' }, { server: 'app-02', time: '1 day ago', message: 'Memory leak detected', severity: 'warning' } ]; return incidents.map(incident => ` <div class="timeline-item severity-${incident.severity || 'warning'}"> <div class="timeline-time">${incident.time}</div> <div class="timeline-content"> <div class="timeline-server">${incident.server}</div> <div class="timeline-message">${incident.message}</div> </div> </div> `).join(''); } } // 차트 매니저 초기화 document.addEventListener('DOMContentLoaded', () => { window.chartManager = new ChartManager(); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/skyasu2/openmanager-vibe-v4'

If you have feedback or need assistance with the MCP directory API, please join our Discord server