feat: monitoring view
This commit is contained in:
@@ -15,7 +15,8 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
const clusterViewModel = new ClusterViewModel();
|
||||
const firmwareViewModel = new FirmwareViewModel();
|
||||
const topologyViewModel = new TopologyViewModel();
|
||||
logger.debug('App: View models created:', { clusterViewModel, firmwareViewModel, topologyViewModel });
|
||||
const monitoringViewModel = new MonitoringViewModel();
|
||||
logger.debug('App: View models created:', { clusterViewModel, firmwareViewModel, topologyViewModel, monitoringViewModel });
|
||||
|
||||
// Connect firmware view model to cluster data
|
||||
clusterViewModel.subscribe('members', (members) => {
|
||||
@@ -40,6 +41,7 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
app.registerRoute('cluster', ClusterViewComponent, 'cluster-view', clusterViewModel);
|
||||
app.registerRoute('topology', TopologyGraphComponent, 'topology-view', topologyViewModel);
|
||||
app.registerRoute('firmware', FirmwareViewComponent, 'firmware-view', firmwareViewModel);
|
||||
app.registerRoute('monitoring', MonitoringViewComponent, 'monitoring-view', monitoringViewModel);
|
||||
logger.debug('App: Routes registered and components pre-initialized');
|
||||
|
||||
// Initialize cluster status component for header badge
|
||||
|
||||
346
public/scripts/components/MonitoringViewComponent.js
Normal file
346
public/scripts/components/MonitoringViewComponent.js
Normal file
@@ -0,0 +1,346 @@
|
||||
// Monitoring View Component
|
||||
class MonitoringViewComponent extends Component {
|
||||
constructor(container, viewModel, eventBus) {
|
||||
super(container, viewModel, eventBus);
|
||||
|
||||
logger.debug('MonitoringViewComponent: Constructor called');
|
||||
logger.debug('MonitoringViewComponent: Container:', container);
|
||||
logger.debug('MonitoringViewComponent: Container ID:', container?.id);
|
||||
|
||||
// Track if we've already loaded data to prevent unnecessary reloads
|
||||
this.dataLoaded = false;
|
||||
}
|
||||
|
||||
mount() {
|
||||
logger.debug('MonitoringViewComponent: Mounting...');
|
||||
super.mount();
|
||||
|
||||
// Set up refresh button event listener
|
||||
this.setupRefreshButton();
|
||||
|
||||
// Only load data if we haven't already or if the view model is empty
|
||||
const clusterMembers = this.viewModel.get('clusterMembers');
|
||||
if (!this.dataLoaded || !clusterMembers || clusterMembers.length === 0) {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
// Subscribe to view model changes
|
||||
this.setupSubscriptions();
|
||||
}
|
||||
|
||||
setupRefreshButton() {
|
||||
const refreshBtn = this.findElement('#refresh-monitoring-btn');
|
||||
if (refreshBtn) {
|
||||
refreshBtn.addEventListener('click', () => {
|
||||
this.refreshData();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setupSubscriptions() {
|
||||
// Subscribe to cluster members changes
|
||||
this.viewModel.subscribe('clusterMembers', () => {
|
||||
this.render();
|
||||
});
|
||||
|
||||
// Subscribe to node resources changes
|
||||
this.viewModel.subscribe('nodeResources', () => {
|
||||
this.render();
|
||||
});
|
||||
|
||||
// Subscribe to cluster summary changes
|
||||
this.viewModel.subscribe('clusterSummary', () => {
|
||||
this.render();
|
||||
});
|
||||
|
||||
// Subscribe to loading state changes
|
||||
this.viewModel.subscribe('isLoading', () => {
|
||||
this.render();
|
||||
});
|
||||
|
||||
// Subscribe to error changes
|
||||
this.viewModel.subscribe('error', () => {
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
logger.debug('MonitoringViewComponent: Loading data...');
|
||||
this.dataLoaded = true;
|
||||
await this.viewModel.loadClusterData();
|
||||
}
|
||||
|
||||
async refreshData() {
|
||||
logger.debug('MonitoringViewComponent: Refreshing data...');
|
||||
await this.viewModel.refresh();
|
||||
}
|
||||
|
||||
render() {
|
||||
logger.debug('MonitoringViewComponent: Rendering...');
|
||||
|
||||
const isLoading = this.viewModel.get('isLoading');
|
||||
const error = this.viewModel.get('error');
|
||||
const clusterSummary = this.viewModel.get('clusterSummary');
|
||||
const nodeResources = this.viewModel.get('nodeResources');
|
||||
const lastUpdated = this.viewModel.get('lastUpdated');
|
||||
|
||||
// Render cluster summary
|
||||
this.renderClusterSummary(isLoading, error, clusterSummary, lastUpdated);
|
||||
|
||||
// Render nodes monitoring
|
||||
this.renderNodesMonitoring(isLoading, error, nodeResources);
|
||||
}
|
||||
|
||||
renderClusterSummary(isLoading, error, clusterSummary, lastUpdated) {
|
||||
const container = this.findElement('#cluster-summary');
|
||||
if (!container) return;
|
||||
|
||||
if (isLoading) {
|
||||
container.innerHTML = `
|
||||
<div class="loading">
|
||||
<div>Loading cluster resource summary...</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
container.innerHTML = `
|
||||
<div class="error">
|
||||
<div>❌ Error: ${error}</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const cpuUtilization = this.viewModel.getResourceUtilization('Cpu');
|
||||
const memoryUtilization = this.viewModel.getResourceUtilization('Memory');
|
||||
const storageUtilization = this.viewModel.getResourceUtilization('Storage');
|
||||
|
||||
const lastUpdatedText = lastUpdated ?
|
||||
`Last updated: ${lastUpdated.toLocaleTimeString()}` :
|
||||
'Never updated';
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="cluster-summary-content">
|
||||
<div class="summary-header">
|
||||
<h3>Summary</h3>
|
||||
<div class="last-updated">${lastUpdatedText}</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">🖥️</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">Total Nodes</div>
|
||||
<div class="stat-value">${clusterSummary.totalNodes}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">⚡</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">CPU</div>
|
||||
<div class="stat-value">${Math.round(clusterSummary.totalCpu - clusterSummary.availableCpu)}MHz / ${Math.round(clusterSummary.totalCpu)}MHz</div>
|
||||
<div class="stat-utilization">
|
||||
<div class="utilization-bar">
|
||||
<div class="utilization-fill" style="width: ${cpuUtilization}%"></div>
|
||||
</div>
|
||||
<span class="utilization-text">${cpuUtilization}% used</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">🧠</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">Memory</div>
|
||||
<div class="stat-value">${this.viewModel.formatResourceValue(clusterSummary.totalMemory - clusterSummary.availableMemory, 'memory')} / ${this.viewModel.formatResourceValue(clusterSummary.totalMemory, 'memory')}</div>
|
||||
<div class="stat-utilization">
|
||||
<div class="utilization-bar">
|
||||
<div class="utilization-fill" style="width: ${memoryUtilization}%"></div>
|
||||
</div>
|
||||
<span class="utilization-text">${memoryUtilization}% used</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">💾</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">Storage</div>
|
||||
<div class="stat-value">${this.viewModel.formatResourceValue(clusterSummary.totalStorage - clusterSummary.availableStorage, 'storage')} / ${this.viewModel.formatResourceValue(clusterSummary.totalStorage, 'storage')}</div>
|
||||
<div class="stat-utilization">
|
||||
<div class="utilization-bar">
|
||||
<div class="utilization-fill" style="width: ${storageUtilization}%"></div>
|
||||
</div>
|
||||
<span class="utilization-text">${storageUtilization}% used</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderNodesMonitoring(isLoading, error, nodeResources) {
|
||||
const container = this.findElement('#nodes-monitoring');
|
||||
if (!container) return;
|
||||
|
||||
if (isLoading) {
|
||||
container.innerHTML = `
|
||||
<div class="loading">
|
||||
<div>Loading node resource data...</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
container.innerHTML = `
|
||||
<div class="error">
|
||||
<div>❌ Error: ${error}</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nodeResources || nodeResources.size === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="no-data">
|
||||
<div>No node resource data available</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const nodesHtml = Array.from(nodeResources.values()).map(nodeData => {
|
||||
return this.renderNodeCard(nodeData);
|
||||
}).join('');
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="nodes-monitoring-content">
|
||||
<h3>🖥️ Node Resource Details</h3>
|
||||
<div class="nodes-grid">
|
||||
${nodesHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderNodeCard(nodeData) {
|
||||
const { ip, hostname, resources, hasResources, error, resourceSource } = nodeData;
|
||||
|
||||
if (!hasResources) {
|
||||
return `
|
||||
<div class="node-card error">
|
||||
<div class="node-header">
|
||||
<div class="node-title">${hostname || ip}</div>
|
||||
<div class="node-ip">${ip}</div>
|
||||
</div>
|
||||
<div class="node-status">
|
||||
<span class="status-badge error">❌ No Resources</span>
|
||||
</div>
|
||||
<div class="node-error">
|
||||
${error || 'Monitoring endpoint not available'}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Extract resource data (handle both monitoring API and basic data formats)
|
||||
const cpu = resources?.cpu || {};
|
||||
const memory = resources?.memory || {};
|
||||
const storage = resources?.filesystem || resources?.storage || {};
|
||||
|
||||
let cpuTotal, cpuAvailable, cpuUsed, cpuUtilization;
|
||||
let memoryTotal, memoryAvailable, memoryUsed, memoryUtilization;
|
||||
let storageTotal, storageAvailable, storageUsed, storageUtilization;
|
||||
|
||||
if (resourceSource === 'monitoring') {
|
||||
// Real monitoring API format
|
||||
const cpuFreqMHz = nodeData.basic?.cpuFreqMHz || 80; // Get CPU frequency from basic resources
|
||||
cpuTotal = cpuFreqMHz; // Total CPU frequency in MHz
|
||||
cpuAvailable = cpuFreqMHz * (100 - (cpu.average_usage || 0)) / 100; // Available frequency
|
||||
cpuUsed = cpuFreqMHz * (cpu.average_usage || 0) / 100; // Used frequency
|
||||
cpuUtilization = Math.round(cpu.average_usage || 0);
|
||||
|
||||
memoryTotal = memory.total_heap || 0;
|
||||
memoryAvailable = memory.free_heap || 0;
|
||||
memoryUsed = memoryTotal - memoryAvailable;
|
||||
memoryUtilization = memoryTotal > 0 ? Math.round((memoryUsed / memoryTotal) * 100) : 0;
|
||||
|
||||
storageTotal = storage.total_bytes || 0;
|
||||
storageAvailable = storage.free_bytes || 0;
|
||||
storageUsed = storageTotal - storageAvailable;
|
||||
storageUtilization = storageTotal > 0 ? Math.round((storageUsed / storageTotal) * 100) : 0;
|
||||
} else {
|
||||
// Basic data format - use CPU frequency from basic resources
|
||||
const cpuFreqMHz = nodeData.basic?.cpuFreqMHz || 80;
|
||||
cpuTotal = cpuFreqMHz; // Total CPU frequency in MHz
|
||||
cpuAvailable = cpuFreqMHz * (cpu.available || 0.8); // Available frequency
|
||||
cpuUsed = cpuFreqMHz * (cpu.used || 0.2); // Used frequency
|
||||
cpuUtilization = cpuTotal > 0 ? Math.round((cpuUsed / cpuTotal) * 100) : 0;
|
||||
|
||||
memoryTotal = memory.total || 0;
|
||||
memoryAvailable = memory.available || memory.free || 0;
|
||||
memoryUsed = memoryTotal - memoryAvailable;
|
||||
memoryUtilization = memoryTotal > 0 ? Math.round((memoryUsed / memoryTotal) * 100) : 0;
|
||||
|
||||
storageTotal = storage.total || 0;
|
||||
storageAvailable = storage.available || storage.free || 0;
|
||||
storageUsed = storageTotal - storageAvailable;
|
||||
storageUtilization = storageTotal > 0 ? Math.round((storageUsed / storageTotal) * 100) : 0;
|
||||
}
|
||||
|
||||
const resourceSourceText = resourceSource === 'monitoring' ? '📊 Full Monitoring' :
|
||||
resourceSource === 'basic' ? '📋 Basic Data' : '❓ Unknown';
|
||||
|
||||
return `
|
||||
<div class="node-card">
|
||||
<div class="node-header">
|
||||
<div class="node-title">${hostname || ip}</div>
|
||||
<div class="node-ip">${ip}</div>
|
||||
</div>
|
||||
<div class="node-status">
|
||||
<span class="status-badge ${resourceSource === 'monitoring' ? 'success' : 'warning'}">${resourceSourceText}</span>
|
||||
</div>
|
||||
|
||||
<div class="node-resources">
|
||||
<div class="resource-item">
|
||||
<div class="resource-label">⚡ CPU</div>
|
||||
<div class="resource-value">${Math.round(cpuUsed)}MHz / ${Math.round(cpuTotal)}MHz</div>
|
||||
<div class="resource-utilization">
|
||||
<div class="utilization-bar">
|
||||
<div class="utilization-fill" style="width: ${cpuUtilization}%"></div>
|
||||
</div>
|
||||
<span class="utilization-text">${cpuUtilization}% used</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-item">
|
||||
<div class="resource-label">🧠 Memory</div>
|
||||
<div class="resource-value">${this.viewModel.formatResourceValue(memoryUsed, 'memory')} / ${this.viewModel.formatResourceValue(memoryTotal, 'memory')}</div>
|
||||
<div class="resource-utilization">
|
||||
<div class="utilization-bar">
|
||||
<div class="utilization-fill" style="width: ${memoryUtilization}%"></div>
|
||||
</div>
|
||||
<span class="utilization-text">${memoryUtilization}% used</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-item">
|
||||
<div class="resource-label">💾 Storage</div>
|
||||
<div class="resource-value">${this.viewModel.formatResourceValue(storageUsed, 'storage')} / ${this.viewModel.formatResourceValue(storageTotal, 'storage')}</div>
|
||||
<div class="resource-utilization">
|
||||
<div class="utilization-bar">
|
||||
<div class="utilization-fill" style="width: ${storageUtilization}%"></div>
|
||||
</div>
|
||||
<span class="utilization-text">${storageUtilization}% used</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -480,7 +480,7 @@ class NavigationViewModel extends ViewModel {
|
||||
super();
|
||||
this.setMultiple({
|
||||
activeView: 'cluster',
|
||||
views: ['cluster', 'firmware']
|
||||
views: ['cluster', 'topology', 'firmware', 'monitoring']
|
||||
});
|
||||
}
|
||||
|
||||
@@ -651,4 +651,231 @@ class TopologyViewModel extends ViewModel {
|
||||
clearSelection() {
|
||||
this.set('selectedNode', null);
|
||||
}
|
||||
}
|
||||
|
||||
// Monitoring View Model for cluster resource monitoring
|
||||
class MonitoringViewModel extends ViewModel {
|
||||
constructor() {
|
||||
super();
|
||||
this.setMultiple({
|
||||
clusterMembers: [],
|
||||
nodeResources: new Map(), // Map of node IP -> resource data
|
||||
clusterSummary: {
|
||||
totalCpu: 0,
|
||||
totalMemory: 0,
|
||||
totalStorage: 0,
|
||||
totalNodes: 0,
|
||||
availableCpu: 0,
|
||||
availableMemory: 0,
|
||||
availableStorage: 0
|
||||
},
|
||||
isLoading: false,
|
||||
lastUpdated: null,
|
||||
error: null
|
||||
});
|
||||
}
|
||||
|
||||
// Load cluster members and their resource data
|
||||
async loadClusterData() {
|
||||
this.set('isLoading', true);
|
||||
this.set('error', null);
|
||||
|
||||
try {
|
||||
// Get cluster members
|
||||
const response = await window.apiClient.getClusterMembers();
|
||||
// Extract members array from response object
|
||||
const members = response.members || [];
|
||||
this.set('clusterMembers', members);
|
||||
|
||||
// Load resource data for each node
|
||||
await this.loadNodeResources(members);
|
||||
|
||||
// Calculate cluster summary
|
||||
this.calculateClusterSummary();
|
||||
|
||||
this.set('lastUpdated', new Date());
|
||||
} catch (error) {
|
||||
console.error('Failed to load cluster monitoring data:', error);
|
||||
this.set('error', error.message || 'Failed to load monitoring data');
|
||||
} finally {
|
||||
this.set('isLoading', false);
|
||||
}
|
||||
}
|
||||
|
||||
// Load resource data for all nodes
|
||||
async loadNodeResources(members) {
|
||||
const nodeResources = new Map();
|
||||
|
||||
// Process nodes in parallel
|
||||
const resourcePromises = members.map(async (member) => {
|
||||
try {
|
||||
const resources = await window.apiClient.getMonitoringResources(member.ip);
|
||||
// Handle both real API (wrapped in data) and mock API (direct response)
|
||||
const resourceData = (resources && resources.data) ? resources.data : resources;
|
||||
nodeResources.set(member.ip, {
|
||||
...member,
|
||||
resources: resourceData,
|
||||
hasResources: true,
|
||||
resourceSource: 'monitoring'
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load monitoring resources for node ${member.ip}:`, error);
|
||||
// Fall back to basic resource data from cluster members API
|
||||
const basicResources = member.resources ? this.convertBasicResources(member.resources) : null;
|
||||
nodeResources.set(member.ip, {
|
||||
...member,
|
||||
resources: basicResources,
|
||||
hasResources: !!basicResources,
|
||||
resourceSource: basicResources ? 'basic' : 'none',
|
||||
error: basicResources ? null : error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(resourcePromises);
|
||||
this.set('nodeResources', nodeResources);
|
||||
}
|
||||
|
||||
// Convert basic resource data from cluster members API to monitoring format
|
||||
convertBasicResources(basicResources) {
|
||||
// Convert ESP32 basic resources to monitoring format
|
||||
const freeHeap = basicResources.freeHeap || 0;
|
||||
const flashSize = basicResources.flashChipSize || 0;
|
||||
const cpuFreq = basicResources.cpuFreqMHz || 80;
|
||||
|
||||
// Estimate total heap (ESP32 typically has ~300KB heap)
|
||||
const estimatedTotalHeap = 300 * 1024; // 300KB in bytes
|
||||
const usedHeap = estimatedTotalHeap - freeHeap;
|
||||
|
||||
return {
|
||||
cpu: {
|
||||
total: cpuFreq, // Total CPU frequency in MHz
|
||||
available: cpuFreq * 0.8, // Estimate 80% available
|
||||
used: cpuFreq * 0.2
|
||||
},
|
||||
memory: {
|
||||
total: estimatedTotalHeap,
|
||||
available: freeHeap,
|
||||
used: usedHeap
|
||||
},
|
||||
storage: {
|
||||
total: flashSize,
|
||||
available: flashSize * 0.5, // Estimate 50% available
|
||||
used: flashSize * 0.5
|
||||
},
|
||||
// Include original basic resources for reference
|
||||
basic: basicResources
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate cluster resource summary
|
||||
calculateClusterSummary() {
|
||||
const nodeResources = this.get('nodeResources');
|
||||
const members = this.get('clusterMembers');
|
||||
|
||||
let totalCpu = 0;
|
||||
let totalMemory = 0;
|
||||
let totalStorage = 0;
|
||||
let availableCpu = 0;
|
||||
let availableMemory = 0;
|
||||
let availableStorage = 0;
|
||||
let totalNodes = 0;
|
||||
|
||||
for (const [ip, nodeData] of nodeResources) {
|
||||
if (nodeData.hasResources && nodeData.resources) {
|
||||
totalNodes++;
|
||||
|
||||
// Extract resource data (handle both monitoring API and basic data formats)
|
||||
const resources = nodeData.resources;
|
||||
|
||||
// CPU resources
|
||||
if (resources.cpu) {
|
||||
const cpuFreqMHz = nodeData.basic?.cpuFreqMHz || 80; // Get CPU frequency from basic resources
|
||||
if (nodeData.resourceSource === 'monitoring') {
|
||||
// Real monitoring API format
|
||||
totalCpu += cpuFreqMHz; // Total CPU frequency in MHz
|
||||
availableCpu += cpuFreqMHz * (100 - (resources.cpu.average_usage || 0)) / 100; // Available frequency
|
||||
} else {
|
||||
// Basic data format
|
||||
totalCpu += cpuFreqMHz; // Total CPU frequency in MHz
|
||||
availableCpu += cpuFreqMHz * (resources.cpu.available || 0.8); // Available frequency
|
||||
}
|
||||
}
|
||||
|
||||
// Memory resources
|
||||
if (resources.memory) {
|
||||
if (nodeData.resourceSource === 'monitoring') {
|
||||
// Real monitoring API format
|
||||
totalMemory += resources.memory.total_heap || 0;
|
||||
availableMemory += resources.memory.free_heap || 0;
|
||||
} else {
|
||||
// Basic data format
|
||||
totalMemory += resources.memory.total || 0;
|
||||
availableMemory += resources.memory.available || 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Storage resources
|
||||
if (resources.filesystem || resources.storage) {
|
||||
const storage = resources.filesystem || resources.storage;
|
||||
if (nodeData.resourceSource === 'monitoring') {
|
||||
// Real monitoring API format
|
||||
totalStorage += storage.total_bytes || 0;
|
||||
availableStorage += storage.free_bytes || 0;
|
||||
} else {
|
||||
// Basic data format
|
||||
totalStorage += storage.total || 0;
|
||||
availableStorage += storage.available || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.set('clusterSummary', {
|
||||
totalCpu,
|
||||
totalMemory,
|
||||
totalStorage,
|
||||
totalNodes,
|
||||
availableCpu,
|
||||
availableMemory,
|
||||
availableStorage
|
||||
});
|
||||
}
|
||||
|
||||
// Get resource utilization percentage
|
||||
getResourceUtilization(resourceType) {
|
||||
const summary = this.get('clusterSummary');
|
||||
const total = summary[`total${resourceType}`];
|
||||
const available = summary[`available${resourceType}`];
|
||||
|
||||
if (total === 0) return 0;
|
||||
return Math.round(((total - available) / total) * 100);
|
||||
}
|
||||
|
||||
// Get formatted resource value
|
||||
formatResourceValue(value, type) {
|
||||
if (type === 'memory' || type === 'storage') {
|
||||
// Convert bytes to human readable format
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
let size = value;
|
||||
let unitIndex = 0;
|
||||
|
||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||
size /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
||||
} else if (type === 'cpu') {
|
||||
// CPU is typically in cores or percentage
|
||||
return `${value} cores`;
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
// Refresh monitoring data
|
||||
async refresh() {
|
||||
await this.loadClusterData();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user