feat: monitoring view

This commit is contained in:
2025-09-20 12:49:44 +02:00
parent e0e86f88a9
commit c13d544e54
5 changed files with 920 additions and 3 deletions

View File

@@ -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();
}
}