3d47fc1439
- Created ResourceHub.cs for SignalR group management. - Developed a modern web dashboard using Tailwind CSS for responsive design. - Implemented real-time updates with SignalR for CPU, Memory, GPU, and Network usage. - Added REST API endpoints for resource information and process management. - Integrated process management features to view and terminate high-usage processes. - Enhanced UI with loading spinners, notifications, and responsive tables. - Included performance charts for historical CPU and Memory usage. - Configured Swagger UI for API documentation. - Established security features including process kill restrictions and API key authentication.
634 lines
25 KiB
JavaScript
634 lines
25 KiB
JavaScript
// Dashboard JavaScript
|
|
class ResourceDashboard {
|
|
constructor() {
|
|
this.connection = null;
|
|
this.cpuChart = null;
|
|
this.memoryChart = null;
|
|
this.cpuHistory = [];
|
|
this.memoryHistory = [];
|
|
this.maxHistoryPoints = 20;
|
|
this.lastResourceData = null; // Store latest resource data
|
|
this.lastSystemInfo = null; // Store latest system info
|
|
this.autoRefreshEnabled = true; // Auto-refresh toggle
|
|
this.refreshInterval = null; // Manual refresh interval
|
|
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
this.setupEventListeners();
|
|
this.initializeCharts();
|
|
await this.connectSignalR();
|
|
await this.loadInitialData();
|
|
this.hideLoading();
|
|
this.startAutoRefresh(); // Use our new auto-refresh system
|
|
}
|
|
|
|
setupEventListeners() {
|
|
document.getElementById('toggleAutoRefresh').addEventListener('click', () => {
|
|
this.toggleAutoRefresh();
|
|
});
|
|
|
|
document.getElementById('toggleProcesses').addEventListener('click', () => {
|
|
this.toggleProcessesSection();
|
|
});
|
|
|
|
document.getElementById('toggleDetails').addEventListener('click', () => {
|
|
this.toggleDetailsSection();
|
|
});
|
|
|
|
document.getElementById('refreshData').addEventListener('click', () => {
|
|
this.refreshData();
|
|
});
|
|
}
|
|
|
|
toggleDetailsSection() {
|
|
const detailsSection = document.getElementById('detailsSection');
|
|
const toggleButton = document.getElementById('toggleDetails');
|
|
|
|
if (detailsSection.classList.contains('hidden')) {
|
|
detailsSection.classList.remove('hidden');
|
|
toggleButton.innerHTML = '<i class="fas fa-info-circle mr-2"></i>Hide Details';
|
|
// Refresh disk usage when details section becomes visible
|
|
this.refreshDetailsData();
|
|
} else {
|
|
detailsSection.classList.add('hidden');
|
|
toggleButton.innerHTML = '<i class="fas fa-info-circle mr-2"></i>Details';
|
|
}
|
|
}
|
|
|
|
toggleProcessesSection() {
|
|
const processesSection = document.getElementById('processesSection');
|
|
const toggleButton = document.getElementById('toggleProcesses');
|
|
|
|
if (processesSection.classList.contains('hidden')) {
|
|
processesSection.classList.remove('hidden');
|
|
toggleButton.innerHTML = '<i class="fas fa-list mr-2"></i>Hide Processes';
|
|
// Refresh processes when section becomes visible
|
|
this.refreshProcessesData();
|
|
} else {
|
|
processesSection.classList.add('hidden');
|
|
toggleButton.innerHTML = '<i class="fas fa-list mr-2"></i>Processes';
|
|
}
|
|
}
|
|
|
|
async refreshProcessesData() {
|
|
try {
|
|
// If we have cached data, use it immediately
|
|
if (this.lastResourceData && this.lastResourceData.topProcesses) {
|
|
this.updateProcessTable(this.lastResourceData.topProcesses);
|
|
}
|
|
|
|
// Then fetch fresh data
|
|
const resourceUsage = await this.fetchData('/api/resource/usage');
|
|
this.updateProcessTable(resourceUsage.topProcesses);
|
|
} catch (error) {
|
|
console.error('Error refreshing processes data:', error);
|
|
}
|
|
}
|
|
|
|
async refreshDetailsData() {
|
|
try {
|
|
// If we have cached data, use it immediately
|
|
if (this.lastResourceData && this.lastResourceData.disks) {
|
|
this.updateDiskUsage(this.lastResourceData.disks);
|
|
}
|
|
if (this.lastSystemInfo) {
|
|
this.updateSystemInfo(this.lastSystemInfo);
|
|
}
|
|
|
|
// Then fetch fresh data
|
|
const [resourceUsage, systemInfo] = await Promise.all([
|
|
this.fetchData('/api/resource/usage'),
|
|
this.fetchData('/api/resource/system-info')
|
|
]);
|
|
|
|
this.updateDiskUsage(resourceUsage.disks);
|
|
this.updateSystemInfo(systemInfo);
|
|
} catch (error) {
|
|
console.error('Error refreshing details data:', error);
|
|
}
|
|
}
|
|
|
|
toggleAutoRefresh() {
|
|
this.autoRefreshEnabled = !this.autoRefreshEnabled;
|
|
const toggleButton = document.getElementById('toggleAutoRefresh');
|
|
|
|
if (this.autoRefreshEnabled) {
|
|
toggleButton.innerHTML = '<i class="fas fa-sync mr-2"></i>Auto: ON';
|
|
toggleButton.className = 'bg-yellow-500 hover:bg-yellow-700 px-4 py-2 rounded-lg transition-colors';
|
|
this.startAutoRefresh();
|
|
} else {
|
|
toggleButton.innerHTML = '<i class="fas fa-pause mr-2"></i>Auto: OFF';
|
|
toggleButton.className = 'bg-gray-500 hover:bg-gray-700 px-4 py-2 rounded-lg transition-colors';
|
|
this.stopAutoRefresh();
|
|
}
|
|
|
|
console.log(`Auto-refresh ${this.autoRefreshEnabled ? 'enabled' : 'disabled'}`);
|
|
}
|
|
|
|
startAutoRefresh() {
|
|
// Connect SignalR for real-time updates
|
|
if (!this.connection || this.connection.state === 'Disconnected') {
|
|
this.connectSignalR();
|
|
}
|
|
|
|
// Also start a fallback manual refresh timer (every 60 seconds as backup)
|
|
if (this.refreshInterval) {
|
|
clearInterval(this.refreshInterval);
|
|
}
|
|
this.refreshInterval = setInterval(() => {
|
|
if (this.autoRefreshEnabled) {
|
|
this.refreshData();
|
|
}
|
|
}, 60000); // 60 second fallback
|
|
}
|
|
|
|
stopAutoRefresh() {
|
|
// Disconnect SignalR
|
|
if (this.connection && this.connection.state === 'Connected') {
|
|
this.connection.stop();
|
|
}
|
|
|
|
// Clear manual refresh timer
|
|
if (this.refreshInterval) {
|
|
clearInterval(this.refreshInterval);
|
|
this.refreshInterval = null;
|
|
}
|
|
}
|
|
|
|
async connectSignalR() {
|
|
try {
|
|
// Only connect if auto-refresh is enabled
|
|
if (!this.autoRefreshEnabled) {
|
|
console.log("SignalR connection skipped - auto-refresh disabled");
|
|
return;
|
|
}
|
|
|
|
this.connection = new signalR.HubConnectionBuilder()
|
|
.withUrl("/resourceHub")
|
|
.build();
|
|
|
|
this.connection.on("ResourceUpdate", (data) => {
|
|
if (this.autoRefreshEnabled) {
|
|
this.updateDashboard(data);
|
|
}
|
|
});
|
|
|
|
await this.connection.start();
|
|
console.log("SignalR Connected");
|
|
} catch (err) {
|
|
console.error("SignalR Connection Error: ", err);
|
|
}
|
|
}
|
|
|
|
async loadInitialData() {
|
|
try {
|
|
const [resourceUsage, systemInfo] = await Promise.all([
|
|
this.fetchData('/api/resource/usage'),
|
|
this.fetchData('/api/resource/system-info')
|
|
]);
|
|
|
|
this.updateDashboard(resourceUsage);
|
|
this.updateSystemInfo(systemInfo);
|
|
} catch (error) {
|
|
console.error('Error loading initial data:', error);
|
|
}
|
|
}
|
|
|
|
async fetchData(url) {
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
return await response.json();
|
|
}
|
|
|
|
updateDashboard(data) {
|
|
try {
|
|
// Store the latest data for when details section is opened
|
|
this.lastResourceData = data;
|
|
|
|
// Update CPU
|
|
if (data.cpu) {
|
|
const cpuUsage = data.cpu.usage || 0;
|
|
document.getElementById('cpuUsage').textContent = `${cpuUsage.toFixed(1)}%`;
|
|
document.getElementById('cpuBar').style.width = `${cpuUsage}%`;
|
|
this.updateCpuChart(cpuUsage);
|
|
}
|
|
|
|
// Update Memory
|
|
if (data.memory) {
|
|
const memUsage = data.memory.usagePercentage || 0;
|
|
document.getElementById('memoryUsage').textContent = `${memUsage.toFixed(1)}%`;
|
|
document.getElementById('memoryBar').style.width = `${memUsage}%`;
|
|
this.updateMemoryChart(memUsage);
|
|
}
|
|
|
|
// Update GPU
|
|
if (data.gpu) {
|
|
const gpuUsage = data.gpu.usage || 0;
|
|
document.getElementById('gpuUsage').textContent = `${gpuUsage.toFixed(1)}%`;
|
|
document.getElementById('gpuBar').style.width = `${gpuUsage}%`;
|
|
}
|
|
|
|
// Update Network
|
|
if (data.network) {
|
|
const bytesReceived = data.network.bytesReceived || 0;
|
|
const bytesSent = data.network.bytesSent || 0;
|
|
const totalSpeed = (bytesReceived + bytesSent) / 1024 / 1024;
|
|
document.getElementById('networkSpeed').textContent = `${totalSpeed.toFixed(1)} MB/s`;
|
|
document.getElementById('networkDetail').textContent =
|
|
`↑ ${(bytesSent / 1024 / 1024).toFixed(1)} MB/s ↓ ${(bytesReceived / 1024 / 1024).toFixed(1)} MB/s`;
|
|
}
|
|
|
|
// Update Game Detection
|
|
if (data.runningGame) {
|
|
this.updateGameInfo(data.runningGame);
|
|
} else {
|
|
this.updateGameInfo(null);
|
|
}
|
|
|
|
// Update Processes (only if processes section is visible)
|
|
if (data.topProcesses && Array.isArray(data.topProcesses) && !document.getElementById('processesSection').classList.contains('hidden')) {
|
|
this.updateProcessTable(data.topProcesses);
|
|
}
|
|
|
|
// Update Disk Usage (only if details section is visible)
|
|
if (data.disks && !document.getElementById('detailsSection').classList.contains('hidden')) {
|
|
this.updateDiskUsage(data.disks);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating dashboard:', error);
|
|
this.showNotification('Error updating dashboard data', 'error');
|
|
}
|
|
}
|
|
|
|
updateProcessTable(processes) {
|
|
const tableBody = document.getElementById('processTable');
|
|
tableBody.innerHTML = '';
|
|
|
|
if (!processes || !Array.isArray(processes)) {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = '<td colspan="4" class="px-4 py-4 text-center text-gray-500">No process data available</td>';
|
|
tableBody.appendChild(row);
|
|
return;
|
|
}
|
|
|
|
processes.slice(0, 10).forEach((process, index) => {
|
|
if (!process) return;
|
|
|
|
const row = document.createElement('tr');
|
|
row.className = 'hover:bg-gray-50';
|
|
|
|
const killButtonClass = index < 3 ?
|
|
'bg-red-500 hover:bg-red-700 text-white px-2 py-1 rounded text-xs transition-colors' :
|
|
'bg-gray-300 text-gray-500 px-2 py-1 rounded text-xs cursor-not-allowed';
|
|
|
|
const killButtonDisabled = index >= 3 ? 'disabled' : '';
|
|
const processName = process.name || 'Unknown';
|
|
const processId = process.processId || 0;
|
|
const cpuUsage = process.cpuUsage || 0;
|
|
const memoryUsage = process.memoryUsage || 0;
|
|
|
|
row.innerHTML = `
|
|
<td class="px-4 py-4 whitespace-nowrap">
|
|
<div class="flex items-center">
|
|
<div class="text-sm font-medium text-gray-900">${processName}</div>
|
|
<div class="text-sm text-gray-500 ml-2">PID: ${processId}</div>
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
${cpuUsage.toFixed(1)}%
|
|
</td>
|
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
${(memoryUsage / 1024 / 1024).toFixed(1)} MB
|
|
</td>
|
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
<button onclick="dashboard.killProcess(${processId}, '${processName}')"
|
|
class="${killButtonClass}" ${killButtonDisabled}>
|
|
<i class="fas fa-times mr-1"></i>Kill
|
|
</button>
|
|
</td>
|
|
`;
|
|
tableBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
async killProcess(processId, processName) {
|
|
if (!confirm(`Are you sure you want to terminate "${processName}" (PID: ${processId})?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/resource/kill-process/${processId}`, {
|
|
method: 'POST'
|
|
});
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
this.showNotification(result.message, 'success');
|
|
await this.refreshData();
|
|
} else {
|
|
const error = await response.text();
|
|
this.showNotification(`Failed to kill process: ${error}`, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error killing process:', error);
|
|
this.showNotification('Error killing process', 'error');
|
|
}
|
|
}
|
|
|
|
updateSystemInfo(systemInfo) {
|
|
// Store the latest system info
|
|
this.lastSystemInfo = systemInfo;
|
|
|
|
const systemInfoDiv = document.getElementById('systemInfo');
|
|
systemInfoDiv.innerHTML = `
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="font-semibold text-gray-700">Machine Name</h4>
|
|
<p class="text-gray-600">${systemInfo.machineName || 'N/A'}</p>
|
|
</div>
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="font-semibold text-gray-700">OS Version</h4>
|
|
<p class="text-gray-600">${systemInfo.osVersion || 'N/A'}</p>
|
|
</div>
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="font-semibold text-gray-700">CPU</h4>
|
|
<p class="text-gray-600">${systemInfo.cpuName || 'N/A'}</p>
|
|
</div>
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="font-semibold text-gray-700">Processor Count</h4>
|
|
<p class="text-gray-600">${systemInfo.processorCount || 0} cores</p>
|
|
</div>
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="font-semibold text-gray-700">Total Memory</h4>
|
|
<p class="text-gray-600">${systemInfo.totalPhysicalMemory ? (systemInfo.totalPhysicalMemory / 1024 / 1024 / 1024).toFixed(1) : 'N/A'} GB</p>
|
|
</div>
|
|
<div class="bg-gray-50 p-4 rounded-lg">
|
|
<h4 class="font-semibold text-gray-700">Uptime</h4>
|
|
<p class="text-gray-600">${systemInfo.uptime ? this.formatUptime(systemInfo.uptime) : 'N/A'}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
updateDiskUsage(disks) {
|
|
const diskUsageDiv = document.getElementById('diskUsage');
|
|
diskUsageDiv.innerHTML = '';
|
|
|
|
if (!disks || !Array.isArray(disks)) {
|
|
diskUsageDiv.innerHTML = '<p class="text-gray-500">No disk information available</p>';
|
|
return;
|
|
}
|
|
|
|
disks.forEach(disk => {
|
|
if (!disk || !disk.totalSize || !disk.freeSpace) return;
|
|
|
|
const usagePercentage = ((disk.totalSize - disk.freeSpace) / disk.totalSize * 100);
|
|
const diskName = disk.label ? `${disk.driveLetter} (${disk.label})` : disk.driveLetter || 'Unknown Drive';
|
|
const diskDiv = document.createElement('div');
|
|
diskDiv.className = 'bg-gray-50 p-4 rounded-lg';
|
|
diskDiv.innerHTML = `
|
|
<div class="flex justify-between items-center mb-2">
|
|
<h4 class="font-semibold text-gray-700">${diskName}</h4>
|
|
<span class="text-sm text-gray-500">${usagePercentage.toFixed(1)}% used</span>
|
|
</div>
|
|
<div class="w-full bg-gray-200 rounded-full h-2 mb-2">
|
|
<div class="bg-blue-600 h-2 rounded-full" style="width: ${usagePercentage}%"></div>
|
|
</div>
|
|
<div class="flex justify-between text-sm text-gray-600">
|
|
<span>Free: ${(disk.freeSpace / 1024 / 1024 / 1024).toFixed(1)} GB</span>
|
|
<span>Total: ${(disk.totalSize / 1024 / 1024 / 1024).toFixed(1)} GB</span>
|
|
</div>
|
|
`;
|
|
diskUsageDiv.appendChild(diskDiv);
|
|
});
|
|
}
|
|
|
|
updateGameInfo(gameInfo) {
|
|
console.log('updateGameInfo called with:', gameInfo); // Debug log
|
|
const gameStatusSpan = document.getElementById('gameStatus');
|
|
const gameInfoDiv = document.getElementById('gameInfo');
|
|
|
|
if (!gameInfo) {
|
|
console.log('No game detected, showing default state'); // Debug log
|
|
gameStatusSpan.textContent = 'No game detected';
|
|
gameInfoDiv.innerHTML = `
|
|
<div class="text-center py-8">
|
|
<div class="text-gray-400 mb-4">
|
|
<i class="fas fa-gamepad text-4xl"></i>
|
|
</div>
|
|
<p class="text-gray-500">No game currently running</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
console.log('Game detected:', gameInfo.gameName); // Debug log
|
|
gameStatusSpan.textContent = 'Game detected';
|
|
gameStatusSpan.className = 'text-sm text-green-600 font-semibold';
|
|
|
|
gameInfoDiv.innerHTML = `
|
|
<div class="bg-gradient-to-r from-purple-50 to-blue-50 p-6 rounded-lg">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="flex items-center">
|
|
<div class="bg-green-100 p-3 rounded-full mr-4">
|
|
<i class="fas fa-gamepad text-green-600 text-2xl"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-xl font-bold text-gray-800">${gameInfo.gameName || 'Unknown Game'}</h3>
|
|
<p class="text-gray-600">Running since ${this.formatGameStartTime(gameInfo.startTime)}</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center space-x-3">
|
|
<div class="bg-green-500 text-white px-3 py-1 rounded-full text-sm font-medium">
|
|
<i class="fas fa-circle text-xs mr-1"></i>ACTIVE
|
|
</div>
|
|
<button onclick="dashboard.killProcess(${gameInfo.processId}, '${gameInfo.gameName || 'Unknown Game'}')"
|
|
class="bg-red-500 hover:bg-red-700 text-white px-3 py-2 rounded-lg text-sm font-medium transition-colors">
|
|
<i class="fas fa-times mr-1"></i>End Game
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div class="bg-white p-4 rounded-lg">
|
|
<h4 class="font-semibold text-gray-700 mb-2">Process ID</h4>
|
|
<p class="text-gray-600">${gameInfo.processId || 'N/A'}</p>
|
|
</div>
|
|
<div class="bg-white p-4 rounded-lg">
|
|
<h4 class="font-semibold text-gray-700 mb-2">Memory Usage</h4>
|
|
<p class="text-gray-600">${gameInfo.memoryUsage ? (gameInfo.memoryUsage / 1024 / 1024).toFixed(1) + ' MB' : 'N/A'}</p>
|
|
</div>
|
|
<div class="bg-white p-4 rounded-lg">
|
|
<h4 class="font-semibold text-gray-700 mb-2">CPU Usage</h4>
|
|
<p class="text-gray-600">${gameInfo.cpuUsage ? gameInfo.cpuUsage.toFixed(1) + '%' : 'N/A'}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
formatGameStartTime(startTime) {
|
|
if (!startTime) return 'Unknown';
|
|
try {
|
|
const date = new Date(startTime);
|
|
const now = new Date();
|
|
const diffMs = now - date;
|
|
const diffMins = Math.floor(diffMs / 60000);
|
|
const diffHours = Math.floor(diffMins / 60);
|
|
|
|
if (diffHours > 0) {
|
|
return `${diffHours}h ${diffMins % 60}m ago`;
|
|
} else {
|
|
return `${diffMins}m ago`;
|
|
}
|
|
} catch (error) {
|
|
return 'Unknown';
|
|
}
|
|
}
|
|
|
|
initializeCharts() {
|
|
// CPU Chart
|
|
const cpuCtx = document.getElementById('cpuChart').getContext('2d');
|
|
this.cpuChart = new Chart(cpuCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: [],
|
|
datasets: [{
|
|
label: 'CPU Usage %',
|
|
data: [],
|
|
borderColor: 'rgb(59, 130, 246)',
|
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
max: 100
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Memory Chart
|
|
const memoryCtx = document.getElementById('memoryChart').getContext('2d');
|
|
this.memoryChart = new Chart(memoryCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: [],
|
|
datasets: [{
|
|
label: 'Memory Usage %',
|
|
data: [],
|
|
borderColor: 'rgb(34, 197, 94)',
|
|
backgroundColor: 'rgba(34, 197, 94, 0.1)',
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
max: 100
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
updateCpuChart(cpuUsage) {
|
|
this.cpuHistory.push(cpuUsage);
|
|
if (this.cpuHistory.length > this.maxHistoryPoints) {
|
|
this.cpuHistory.shift();
|
|
}
|
|
|
|
this.cpuChart.data.labels = this.cpuHistory.map((_, index) => '');
|
|
this.cpuChart.data.datasets[0].data = this.cpuHistory;
|
|
this.cpuChart.update('none');
|
|
}
|
|
|
|
updateMemoryChart(memoryUsage) {
|
|
this.memoryHistory.push(memoryUsage);
|
|
if (this.memoryHistory.length > this.maxHistoryPoints) {
|
|
this.memoryHistory.shift();
|
|
}
|
|
|
|
this.memoryChart.data.labels = this.memoryHistory.map((_, index) => '');
|
|
this.memoryChart.data.datasets[0].data = this.memoryHistory;
|
|
this.memoryChart.update('none');
|
|
}
|
|
|
|
async refreshData() {
|
|
try {
|
|
console.log('Manual refresh triggered');
|
|
const resourceUsage = await this.fetchData('/api/resource/usage');
|
|
this.updateDashboard(resourceUsage);
|
|
|
|
// Show brief success feedback
|
|
this.showNotification('Data refreshed successfully', 'success');
|
|
} catch (error) {
|
|
console.error('Error refreshing data:', error);
|
|
this.showNotification('Error refreshing data', 'error');
|
|
}
|
|
}
|
|
|
|
// Legacy method - now handled by startAutoRefresh()
|
|
startDataRefresh() {
|
|
console.log("Using new auto-refresh system instead");
|
|
this.startAutoRefresh();
|
|
}
|
|
|
|
formatUptime(uptime) {
|
|
// Parse TimeSpan string format "d.hh:mm:ss.fffffff"
|
|
if (typeof uptime === 'string') {
|
|
const parts = uptime.split('.');
|
|
const days = parseInt(parts[0]) || 0;
|
|
|
|
if (parts[1]) {
|
|
const timeParts = parts[1].split(':');
|
|
const hours = parseInt(timeParts[0]) || 0;
|
|
const minutes = parseInt(timeParts[1]) || 0;
|
|
|
|
return `${days}d ${hours}h ${minutes}m`;
|
|
}
|
|
return `${days}d 0h 0m`;
|
|
}
|
|
|
|
// Fallback for object format
|
|
const days = Math.floor(uptime.totalDays || 0);
|
|
const hours = Math.floor(uptime.hours || 0);
|
|
const minutes = Math.floor(uptime.minutes || 0);
|
|
|
|
return `${days}d ${hours}h ${minutes}m`;
|
|
}
|
|
|
|
showNotification(message, type = 'info') {
|
|
const notification = document.createElement('div');
|
|
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg ${
|
|
type === 'success' ? 'bg-green-500' :
|
|
type === 'error' ? 'bg-red-500' : 'bg-blue-500'
|
|
} text-white`;
|
|
notification.textContent = message;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.remove();
|
|
}, 3000);
|
|
}
|
|
|
|
hideLoading() {
|
|
document.getElementById('loadingOverlay').style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// Initialize dashboard when page loads
|
|
let dashboard;
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
dashboard = new ResourceDashboard();
|
|
});
|