feat: add node resource infos
This commit is contained in:
@@ -108,6 +108,18 @@ class ApiClient {
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async getMonitoringResources(ip) {
|
||||
return this.request('/api/proxy-call', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
ip: ip,
|
||||
method: 'GET',
|
||||
uri: '/api/monitoring/resources',
|
||||
params: []
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Global API client instance
|
||||
|
||||
@@ -12,12 +12,13 @@ class NodeDetailsComponent extends Component {
|
||||
this.subscribeToProperty('error', this.handleErrorUpdate.bind(this));
|
||||
this.subscribeToProperty('activeTab', this.handleActiveTabUpdate.bind(this));
|
||||
this.subscribeToProperty('endpoints', this.handleEndpointsUpdate.bind(this));
|
||||
this.subscribeToProperty('monitoringResources', this.handleMonitoringResourcesUpdate.bind(this));
|
||||
}
|
||||
|
||||
// Handle node status update with state preservation
|
||||
handleNodeStatusUpdate(newStatus, previousStatus) {
|
||||
if (newStatus && !this.viewModel.get('isLoading')) {
|
||||
this.renderNodeDetails(newStatus, this.viewModel.get('tasks'), this.viewModel.get('endpoints'));
|
||||
this.renderNodeDetails(newStatus, this.viewModel.get('tasks'), this.viewModel.get('endpoints'), this.viewModel.get('monitoringResources'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +26,7 @@ class NodeDetailsComponent extends Component {
|
||||
handleTasksUpdate(newTasks, previousTasks) {
|
||||
const nodeStatus = this.viewModel.get('nodeStatus');
|
||||
if (nodeStatus && !this.viewModel.get('isLoading')) {
|
||||
this.renderNodeDetails(nodeStatus, newTasks, this.viewModel.get('endpoints'));
|
||||
this.renderNodeDetails(nodeStatus, newTasks, this.viewModel.get('endpoints'), this.viewModel.get('monitoringResources'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +56,17 @@ class NodeDetailsComponent extends Component {
|
||||
const nodeStatus = this.viewModel.get('nodeStatus');
|
||||
const tasks = this.viewModel.get('tasks');
|
||||
if (nodeStatus && !this.viewModel.get('isLoading')) {
|
||||
this.renderNodeDetails(nodeStatus, tasks, newEndpoints);
|
||||
this.renderNodeDetails(nodeStatus, tasks, newEndpoints, this.viewModel.get('monitoringResources'));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle monitoring resources update with state preservation
|
||||
handleMonitoringResourcesUpdate(newResources, previousResources) {
|
||||
const nodeStatus = this.viewModel.get('nodeStatus');
|
||||
const tasks = this.viewModel.get('tasks');
|
||||
const endpoints = this.viewModel.get('endpoints');
|
||||
if (nodeStatus && !this.viewModel.get('isLoading')) {
|
||||
this.renderNodeDetails(nodeStatus, tasks, endpoints, newResources);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +76,7 @@ class NodeDetailsComponent extends Component {
|
||||
const isLoading = this.viewModel.get('isLoading');
|
||||
const error = this.viewModel.get('error');
|
||||
const endpoints = this.viewModel.get('endpoints');
|
||||
const monitoringResources = this.viewModel.get('monitoringResources');
|
||||
|
||||
if (isLoading) {
|
||||
this.renderLoading('<div class="loading-details">Loading detailed information...</div>');
|
||||
@@ -81,10 +93,10 @@ class NodeDetailsComponent extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
this.renderNodeDetails(nodeStatus, tasks, endpoints);
|
||||
this.renderNodeDetails(nodeStatus, tasks, endpoints, monitoringResources);
|
||||
}
|
||||
|
||||
renderNodeDetails(nodeStatus, tasks, endpoints) {
|
||||
renderNodeDetails(nodeStatus, tasks, endpoints, monitoringResources) {
|
||||
// Use persisted active tab from the view model, default to 'status'
|
||||
const activeTab = (this.viewModel && typeof this.viewModel.get === 'function' && this.viewModel.get('activeTab')) || 'status';
|
||||
logger.debug('NodeDetailsComponent: Rendering with activeTab:', activeTab);
|
||||
@@ -115,26 +127,7 @@ class NodeDetailsComponent extends Component {
|
||||
</div>
|
||||
|
||||
<div class="tab-content ${activeTab === 'status' ? 'active' : ''}" id="status-tab">
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Free Heap:</span>
|
||||
<span class="detail-value">${Math.round(nodeStatus.freeHeap / 1024)}KB</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Chip ID:</span>
|
||||
<span class="detail-value">${nodeStatus.chipId}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">SDK Version:</span>
|
||||
<span class="detail-value">${nodeStatus.sdkVersion}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">CPU Frequency:</span>
|
||||
<span class="detail-value">${nodeStatus.cpuFreqMHz}MHz</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Flash Size:</span>
|
||||
<span class="detail-value">${Math.round(nodeStatus.flashChipSize / 1024)}KB</span>
|
||||
</div>
|
||||
${this.renderStatusTab(nodeStatus, monitoringResources)}
|
||||
</div>
|
||||
|
||||
<div class="tab-content ${activeTab === 'endpoints' ? 'active' : ''}" id="endpoints-tab">
|
||||
@@ -187,8 +180,11 @@ class NodeDetailsComponent extends Component {
|
||||
await this.viewModel.loadEndpointsData();
|
||||
} else if (activeTab === 'tasks' && typeof this.viewModel.loadTasksData === 'function') {
|
||||
await this.viewModel.loadTasksData();
|
||||
} else if (activeTab === 'status' && typeof this.viewModel.loadMonitoringResources === 'function') {
|
||||
// status tab: load monitoring resources
|
||||
await this.viewModel.loadMonitoringResources();
|
||||
} else {
|
||||
// status or firmware: refresh core node details
|
||||
// firmware: refresh core node details
|
||||
if (nodeIp && typeof this.viewModel.loadNodeDetails === 'function') {
|
||||
await this.viewModel.loadNodeDetails(nodeIp);
|
||||
}
|
||||
@@ -203,6 +199,153 @@ class NodeDetailsComponent extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
renderStatusTab(nodeStatus, monitoringResources) {
|
||||
let html = '';
|
||||
|
||||
// Add gauges section if monitoring resources are available
|
||||
if (monitoringResources) {
|
||||
html += this.renderResourceGauges(monitoringResources);
|
||||
}
|
||||
|
||||
html += `
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Chip ID:</span>
|
||||
<span class="detail-value">${nodeStatus.chipId}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">SDK Version:</span>
|
||||
<span class="detail-value">${nodeStatus.sdkVersion}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">CPU Frequency:</span>
|
||||
<span class="detail-value">${nodeStatus.cpuFreqMHz}MHz</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Flash Size:</span>
|
||||
<span class="detail-value">${Math.round(nodeStatus.flashChipSize / 1024)}KB</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add monitoring resources if available
|
||||
if (monitoringResources) {
|
||||
html += `
|
||||
<div class="monitoring-section">
|
||||
<div class="monitoring-header">Resources</div>
|
||||
`;
|
||||
|
||||
// CPU Usage
|
||||
if (monitoringResources.cpu) {
|
||||
html += `
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">CPU Usage (Avg):</span>
|
||||
<span class="detail-value">${monitoringResources.cpu.average_usage ? monitoringResources.cpu.average_usage.toFixed(1) + '%' : 'N/A'}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Memory Usage
|
||||
if (monitoringResources.memory) {
|
||||
const heapUsagePercent = monitoringResources.memory.heap_usage_percent || 0;
|
||||
const totalHeap = monitoringResources.memory.total_heap || 0;
|
||||
const usedHeap = totalHeap - (monitoringResources.memory.free_heap || 0);
|
||||
const usedHeapKB = Math.round(usedHeap / 1024);
|
||||
const totalHeapKB = Math.round(totalHeap / 1024);
|
||||
|
||||
html += `
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Heap Usage:</span>
|
||||
<span class="detail-value">${heapUsagePercent.toFixed(1)}% (${usedHeapKB}KB / ${totalHeapKB}KB)</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Filesystem Usage
|
||||
if (monitoringResources.filesystem) {
|
||||
const usedKB = Math.round(monitoringResources.filesystem.used_bytes / 1024);
|
||||
const totalKB = Math.round(monitoringResources.filesystem.total_bytes / 1024);
|
||||
html += `
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Filesystem:</span>
|
||||
<span class="detail-value">${monitoringResources.filesystem.usage_percent ? monitoringResources.filesystem.usage_percent.toFixed(1) + '%' : 'N/A'} (${usedKB}KB / ${totalKB}KB)</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// System Uptime
|
||||
if (monitoringResources.system) {
|
||||
html += `
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Uptime:</span>
|
||||
<span class="detail-value">${monitoringResources.system.uptime_formatted || 'N/A'}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
renderResourceGauges(monitoringResources) {
|
||||
// Get values with fallbacks and ensure they are numbers
|
||||
const cpuUsage = parseFloat(monitoringResources.cpu?.average_usage) || 0;
|
||||
const heapUsage = parseFloat(monitoringResources.memory?.heap_usage_percent) || 0;
|
||||
const filesystemUsage = parseFloat(monitoringResources.filesystem?.usage_percent) || 0;
|
||||
const filesystemUsed = parseFloat(monitoringResources.filesystem?.used_bytes) || 0;
|
||||
const filesystemTotal = parseFloat(monitoringResources.filesystem?.total_bytes) || 0;
|
||||
|
||||
// Convert filesystem bytes to KB
|
||||
const filesystemUsedKB = Math.round(filesystemUsed / 1024);
|
||||
const filesystemTotalKB = Math.round(filesystemTotal / 1024);
|
||||
|
||||
// Helper function to get color class based on percentage
|
||||
const getColorClass = (percentage) => {
|
||||
const numPercentage = parseFloat(percentage);
|
||||
|
||||
if (numPercentage === 0 || isNaN(numPercentage)) return 'gauge-empty';
|
||||
if (numPercentage < 50) return 'gauge-green';
|
||||
if (numPercentage < 80) return 'gauge-yellow';
|
||||
return 'gauge-red';
|
||||
};
|
||||
|
||||
return `
|
||||
<div class="resource-gauges">
|
||||
<div class="gauge-container">
|
||||
<div class="gauge ${getColorClass(cpuUsage)}" data-percentage="${cpuUsage}" style="--percentage: ${cpuUsage}">
|
||||
<div class="gauge-circle">
|
||||
<div class="gauge-text">
|
||||
<div class="gauge-value">${cpuUsage.toFixed(1)}%</div>
|
||||
<div class="gauge-label">CPU</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gauge-container">
|
||||
<div class="gauge ${getColorClass(heapUsage)}" data-percentage="${heapUsage}" style="--percentage: ${heapUsage}">
|
||||
<div class="gauge-circle">
|
||||
<div class="gauge-text">
|
||||
<div class="gauge-value">${heapUsage.toFixed(1)}%</div>
|
||||
<div class="gauge-label">Heap</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gauge-container">
|
||||
<div class="gauge ${getColorClass(filesystemUsage)}" data-percentage="${filesystemUsage}" style="--percentage: ${filesystemUsage}">
|
||||
<div class="gauge-circle">
|
||||
<div class="gauge-text">
|
||||
<div class="gauge-value">${filesystemUsage.toFixed(1)}%</div>
|
||||
<div class="gauge-label">Storage</div>
|
||||
<!-- <div class="gauge-detail">${filesystemUsedKB}KB / ${filesystemTotalKB}KB</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderEndpointsTab(endpoints) {
|
||||
if (!endpoints || !Array.isArray(endpoints.endpoints) || endpoints.endpoints.length === 0) {
|
||||
return `
|
||||
|
||||
@@ -220,7 +220,8 @@ class NodeDetailsViewModel extends ViewModel {
|
||||
activeTab: 'status',
|
||||
nodeIp: null,
|
||||
endpoints: null,
|
||||
tasksSummary: null
|
||||
tasksSummary: null,
|
||||
monitoringResources: null
|
||||
});
|
||||
}
|
||||
|
||||
@@ -250,6 +251,9 @@ class NodeDetailsViewModel extends ViewModel {
|
||||
// Load endpoints data
|
||||
await this.loadEndpointsData();
|
||||
|
||||
// Load monitoring resources data
|
||||
await this.loadMonitoringResources();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to load node details:', error);
|
||||
this.set('error', error.message);
|
||||
@@ -284,6 +288,20 @@ class NodeDetailsViewModel extends ViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
// Load monitoring resources data with state preservation
|
||||
async loadMonitoringResources() {
|
||||
try {
|
||||
const ip = this.get('nodeIp');
|
||||
const response = await window.apiClient.getMonitoringResources(ip);
|
||||
// The proxy call returns { data: {...} }, so we need to extract the data
|
||||
const monitoringData = (response && response.data) ? response.data : null;
|
||||
this.set('monitoringResources', monitoringData);
|
||||
} catch (error) {
|
||||
console.error('Failed to load monitoring resources:', error);
|
||||
this.set('monitoringResources', null);
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke an endpoint against this node
|
||||
async callEndpoint(method, uri, params) {
|
||||
const ip = this.get('nodeIp');
|
||||
|
||||
Reference in New Issue
Block a user