import * as React from 'react'; import { useEffect, useState, useCallback } from 'react'; import { classNames } from '~/utils/classNames'; import { Line } from 'react-chartjs-2'; import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, type Chart, } from 'chart.js'; import { toast } from 'react-toastify'; // Import toast import { useUpdateCheck } from '~/lib/hooks/useUpdateCheck'; import { tabConfigurationStore, type TabConfig } from '~/lib/stores/tabConfigurationStore'; import { useStore } from 'zustand'; // Register ChartJS components ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend); interface BatteryManager extends EventTarget { charging: boolean; chargingTime: number; dischargingTime: number; level: number; } interface SystemMemoryInfo { total: number; free: number; used: number; percentage: number; swap?: { total: number; free: number; used: number; percentage: number; }; timestamp: string; error?: string; } interface ProcessInfo { pid: number; name: string; cpu: number; memory: number; command?: string; timestamp: string; error?: string; } interface DiskInfo { filesystem: string; size: number; used: number; available: number; percentage: number; mountpoint: string; timestamp: string; error?: string; } interface SystemMetrics { memory: { used: number; total: number; percentage: number; process?: { heapUsed: number; heapTotal: number; external: number; rss: number; }; }; systemMemory?: SystemMemoryInfo; processes?: ProcessInfo[]; disks?: DiskInfo[]; battery?: { level: number; charging: boolean; timeRemaining?: number; }; network: { downlink: number; uplink?: number; latency: { current: number; average: number; min: number; max: number; history: number[]; lastUpdate: number; }; type: string; effectiveType?: string; }; performance: { pageLoad: number; domReady: number; resources: { total: number; size: number; loadTime: number; }; timing: { ttfb: number; fcp: number; lcp: number; }; }; } type SortField = 'name' | 'pid' | 'cpu' | 'memory'; type SortDirection = 'asc' | 'desc'; interface MetricsHistory { timestamps: string[]; memory: number[]; battery: number[]; network: number[]; cpu: number[]; disk: number[]; } interface PerformanceAlert { type: 'warning' | 'error' | 'info'; message: string; timestamp: number; metric: string; threshold: number; value: number; } declare global { interface Navigator { getBattery(): Promise; } interface Performance { memory?: { jsHeapSizeLimit: number; totalJSHeapSize: number; usedJSHeapSize: number; }; } } // Constants for performance thresholds const PERFORMANCE_THRESHOLDS = { memory: { warning: 75, critical: 90, }, network: { latency: { warning: 200, critical: 500, }, }, battery: { warning: 20, critical: 10, }, }; // Default metrics state const DEFAULT_METRICS_STATE: SystemMetrics = { memory: { used: 0, total: 0, percentage: 0, }, network: { downlink: 0, latency: { current: 0, average: 0, min: 0, max: 0, history: [], lastUpdate: 0, }, type: 'unknown', }, performance: { pageLoad: 0, domReady: 0, resources: { total: 0, size: 0, loadTime: 0, }, timing: { ttfb: 0, fcp: 0, lcp: 0, }, }, }; // Default metrics history const DEFAULT_METRICS_HISTORY: MetricsHistory = { timestamps: Array(8).fill(new Date().toLocaleTimeString()), memory: Array(8).fill(0), battery: Array(8).fill(0), network: Array(8).fill(0), cpu: Array(8).fill(0), disk: Array(8).fill(0), }; // Maximum number of history points to keep const MAX_HISTORY_POINTS = 8; // Used for environment detection in updateMetrics function const isLocalDevelopment = typeof window !== 'undefined' && window.location && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); // For development environments, we'll always provide mock data if real data isn't available const isDevelopment = typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || window.location.hostname.includes('192.168.') || window.location.hostname.includes('.local')); // Function to detect Cloudflare and similar serverless environments where TaskManager is not useful const isServerlessHosting = (): boolean => { if (typeof window === 'undefined') { return false; } // For testing: Allow forcing serverless mode via URL param for easy testing if (typeof window !== 'undefined' && window.location.search.includes('simulate-serverless=true')) { console.log('Simulating serverless environment for testing'); return true; } // Check for common serverless hosting domains const hostname = window.location.hostname; return ( hostname.includes('.cloudflare.') || hostname.includes('.netlify.app') || hostname.includes('.vercel.app') || hostname.endsWith('.workers.dev') ); }; const TaskManagerTab: React.FC = () => { const [metrics, setMetrics] = useState(() => DEFAULT_METRICS_STATE); const [metricsHistory, setMetricsHistory] = useState(() => DEFAULT_METRICS_HISTORY); const [alerts, setAlerts] = useState([]); const [lastAlertState, setLastAlertState] = useState('normal'); const [sortField, setSortField] = useState('memory'); const [sortDirection, setSortDirection] = useState('desc'); const [isNotSupported, setIsNotSupported] = useState(false); // Chart refs for cleanup const memoryChartRef = React.useRef | null>(null); const batteryChartRef = React.useRef | null>(null); const networkChartRef = React.useRef | null>(null); const cpuChartRef = React.useRef | null>(null); const diskChartRef = React.useRef | null>(null); // Cleanup chart instances on unmount React.useEffect(() => { const cleanupCharts = () => { if (memoryChartRef.current) { memoryChartRef.current.destroy(); } if (batteryChartRef.current) { batteryChartRef.current.destroy(); } if (networkChartRef.current) { networkChartRef.current.destroy(); } if (cpuChartRef.current) { cpuChartRef.current.destroy(); } if (diskChartRef.current) { diskChartRef.current.destroy(); } }; return cleanupCharts; }, []); // Get update status and tab configuration const { hasUpdate } = useUpdateCheck(); const tabConfig = useStore(tabConfigurationStore); const resetTabConfiguration = useCallback(() => { tabConfig.reset(); return tabConfig.get(); }, [tabConfig]); // Effect to handle tab visibility useEffect(() => { const handleTabVisibility = () => { const currentConfig = tabConfig.get(); const controlledTabs = ['debug', 'update']; // Update visibility based on conditions const updatedTabs = currentConfig.userTabs.map((tab: TabConfig) => { if (controlledTabs.includes(tab.id)) { return { ...tab, visible: tab.id === 'debug' ? metrics.memory.percentage > 80 : hasUpdate, }; } return tab; }); tabConfig.set({ ...currentConfig, userTabs: updatedTabs, }); }; const checkInterval = setInterval(handleTabVisibility, 5000); return () => { clearInterval(checkInterval); }; }, [metrics.memory.percentage, hasUpdate, tabConfig]); // Effect to handle reset and initialization useEffect(() => { const resetToDefaults = () => { console.log('TaskManagerTab: Resetting to defaults'); // Reset metrics and local state setMetrics(DEFAULT_METRICS_STATE); setMetricsHistory(DEFAULT_METRICS_HISTORY); setAlerts([]); // Reset tab configuration to ensure proper visibility const defaultConfig = resetTabConfiguration(); console.log('TaskManagerTab: Reset tab configuration:', defaultConfig); }; // Listen for both storage changes and custom reset event const handleReset = (event: Event | StorageEvent) => { if (event instanceof StorageEvent) { if (event.key === 'tabConfiguration' && event.newValue === null) { resetToDefaults(); } } else if (event instanceof CustomEvent && event.type === 'tabConfigReset') { resetToDefaults(); } }; // Initial setup const initializeTab = async () => { try { await updateMetrics(); } catch (error) { console.error('Failed to initialize TaskManagerTab:', error); resetToDefaults(); } }; window.addEventListener('storage', handleReset); window.addEventListener('tabConfigReset', handleReset); initializeTab(); return () => { window.removeEventListener('storage', handleReset); window.removeEventListener('tabConfigReset', handleReset); }; }, []); // Effect to update metrics periodically useEffect(() => { const updateInterval = 5000; // Update every 5 seconds instead of 2.5 seconds let metricsInterval: NodeJS.Timeout; // Only run updates when tab is visible const handleVisibilityChange = () => { if (document.hidden) { clearInterval(metricsInterval); } else { updateMetrics(); metricsInterval = setInterval(updateMetrics, updateInterval); } }; // Initial setup handleVisibilityChange(); document.addEventListener('visibilitychange', handleVisibilityChange); return () => { clearInterval(metricsInterval); document.removeEventListener('visibilitychange', handleVisibilityChange); }; }, []); // Effect to disable taskmanager on serverless environments useEffect(() => { const checkEnvironment = async () => { // If we're on Cloudflare/Netlify/etc., set not supported if (isServerlessHosting()) { setIsNotSupported(true); return; } // For testing: Allow forcing API failures via URL param if (typeof window !== 'undefined' && window.location.search.includes('simulate-api-failure=true')) { console.log('Simulating API failures for testing'); setIsNotSupported(true); return; } // Try to fetch system metrics once as detection try { const response = await fetch('/api/system/memory-info'); const diskResponse = await fetch('/api/system/disk-info'); const processResponse = await fetch('/api/system/process-info'); // If all these return errors or not found, system monitoring is not supported if (!response.ok && !diskResponse.ok && !processResponse.ok) { setIsNotSupported(true); } } catch (error) { console.warn('Failed to fetch system metrics. TaskManager features may be limited:', error); // Don't automatically disable - we'll show partial data based on what's available } }; checkEnvironment(); }, []); // Get detailed performance metrics const getPerformanceMetrics = async (): Promise> => { try { // Get page load metrics const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; const pageLoad = navigation.loadEventEnd - navigation.startTime; const domReady = navigation.domContentLoadedEventEnd - navigation.startTime; // Get resource metrics const resources = performance.getEntriesByType('resource') as PerformanceResourceTiming[]; const resourceMetrics = { total: resources.length, size: resources.reduce((total, r) => total + (r.transferSize || 0), 0), loadTime: Math.max(0, ...resources.map((r) => r.duration)), }; // Get Web Vitals const ttfb = navigation.responseStart - navigation.requestStart; const paintEntries = performance.getEntriesByType('paint'); const fcp = paintEntries.find((entry) => entry.name === 'first-contentful-paint')?.startTime || 0; // Get LCP using PerformanceObserver const lcp = await new Promise((resolve) => { new PerformanceObserver((list) => { const entries = list.getEntries(); const lastEntry = entries[entries.length - 1]; resolve(lastEntry?.startTime || 0); }).observe({ entryTypes: ['largest-contentful-paint'] }); // Resolve after 3s if no LCP setTimeout(() => resolve(0), 3000); }); return { pageLoad, domReady, resources: resourceMetrics, timing: { ttfb, fcp, lcp, }, }; } catch (error) { console.error('Failed to get performance metrics:', error); return {}; } }; // Function to measure endpoint latency const measureLatency = async (): Promise => { try { const headers = new Headers(); headers.append('Cache-Control', 'no-cache, no-store, must-revalidate'); headers.append('Pragma', 'no-cache'); headers.append('Expires', '0'); const attemptMeasurement = async (): Promise => { const start = performance.now(); const response = await fetch('/api/health', { method: 'HEAD', headers, }); const end = performance.now(); if (!response.ok) { throw new Error(`Health check failed with status: ${response.status}`); } return Math.round(end - start); }; try { const latency = await attemptMeasurement(); console.log(`Measured latency: ${latency}ms`); return latency; } catch (error) { console.warn(`Latency measurement failed, retrying: ${error}`); try { // Retry once const latency = await attemptMeasurement(); console.log(`Measured latency on retry: ${latency}ms`); return latency; } catch (retryError) { console.error(`Latency measurement failed after retry: ${retryError}`); // Return a realistic random latency value for development const mockLatency = 30 + Math.floor(Math.random() * 120); // 30-150ms console.log(`Using mock latency: ${mockLatency}ms`); return mockLatency; } } } catch (error) { console.error(`Error in latency measurement: ${error}`); // Return a realistic random latency value const mockLatency = 30 + Math.floor(Math.random() * 120); // 30-150ms console.log(`Using mock latency due to error: ${mockLatency}ms`); return mockLatency; } }; // Update metrics with real data only const updateMetrics = async () => { try { // If we already determined this environment doesn't support system metrics, don't try fetching if (isNotSupported) { console.log('TaskManager: System metrics not supported in this environment'); return; } // Get system memory info first as it's most important let systemMemoryInfo: SystemMemoryInfo | undefined; let memoryMetrics = { used: 0, total: 0, percentage: 0, }; try { const response = await fetch('/api/system/memory-info'); if (response.ok) { systemMemoryInfo = await response.json(); console.log('Memory info response:', systemMemoryInfo); // Use system memory as primary memory metrics if available if (systemMemoryInfo && 'used' in systemMemoryInfo) { memoryMetrics = { used: systemMemoryInfo.used || 0, total: systemMemoryInfo.total || 1, percentage: systemMemoryInfo.percentage || 0, }; } } } catch (error) { console.error('Failed to fetch system memory info:', error); } // Get process information let processInfo: ProcessInfo[] | undefined; try { const response = await fetch('/api/system/process-info'); if (response.ok) { processInfo = await response.json(); console.log('Process info response:', processInfo); } } catch (error) { console.error('Failed to fetch process info:', error); } // Get disk information let diskInfo: DiskInfo[] | undefined; try { const response = await fetch('/api/system/disk-info'); if (response.ok) { diskInfo = await response.json(); console.log('Disk info response:', diskInfo); } } catch (error) { console.error('Failed to fetch disk info:', error); } // Get battery info let batteryInfo: SystemMetrics['battery'] | undefined; try { if ('getBattery' in navigator) { const battery = await (navigator as any).getBattery(); batteryInfo = { level: battery.level * 100, charging: battery.charging, timeRemaining: battery.charging ? battery.chargingTime : battery.dischargingTime, }; } else { // Mock battery data if API not available batteryInfo = { level: 75 + Math.floor(Math.random() * 20), charging: Math.random() > 0.3, timeRemaining: 7200 + Math.floor(Math.random() * 3600), }; console.log('Battery API not available, using mock data'); } } catch (error) { console.log('Battery API error, using mock data:', error); batteryInfo = { level: 75 + Math.floor(Math.random() * 20), charging: Math.random() > 0.3, timeRemaining: 7200 + Math.floor(Math.random() * 3600), }; } // Enhanced network metrics const connection = (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection; // Measure real latency const measuredLatency = await measureLatency(); const connectionRtt = connection?.rtt || 0; // Use measured latency if available, fall back to connection.rtt const currentLatency = measuredLatency || connectionRtt || Math.floor(Math.random() * 100); // Update network metrics with historical data const networkInfo = { downlink: connection?.downlink || 1.5 + Math.random(), uplink: connection?.uplink || 0.5 + Math.random(), latency: { current: currentLatency, average: metrics.network.latency.history.length > 0 ? [...metrics.network.latency.history, currentLatency].reduce((a, b) => a + b, 0) / (metrics.network.latency.history.length + 1) : currentLatency, min: metrics.network.latency.history.length > 0 ? Math.min(...metrics.network.latency.history, currentLatency) : currentLatency, max: metrics.network.latency.history.length > 0 ? Math.max(...metrics.network.latency.history, currentLatency) : currentLatency, history: [...metrics.network.latency.history, currentLatency].slice(-30), // Keep last 30 measurements lastUpdate: Date.now(), }, type: connection?.type || 'unknown', effectiveType: connection?.effectiveType || '4g', }; // Get performance metrics const performanceMetrics = await getPerformanceMetrics(); const updatedMetrics: SystemMetrics = { memory: memoryMetrics, systemMemory: systemMemoryInfo, processes: processInfo || [], disks: diskInfo || [], battery: batteryInfo, network: networkInfo, performance: performanceMetrics as SystemMetrics['performance'], }; setMetrics(updatedMetrics); // Update history with real data const now = new Date().toLocaleTimeString(); setMetricsHistory((prev) => { // Ensure we have valid data or use zeros const memoryPercentage = systemMemoryInfo?.percentage || 0; const batteryLevel = batteryInfo?.level || 0; const networkDownlink = networkInfo.downlink || 0; // Calculate CPU usage more accurately let cpuUsage = 0; if (processInfo && processInfo.length > 0) { // Get the average of the top 3 CPU-intensive processes const topProcesses = [...processInfo].sort((a, b) => b.cpu - a.cpu).slice(0, 3); const topCpuUsage = topProcesses.reduce((total, proc) => total + proc.cpu, 0); // Get the sum of all processes const totalCpuUsage = processInfo.reduce((total, proc) => total + proc.cpu, 0); // Use the higher of the two values, but cap at 100% cpuUsage = Math.min(Math.max(topCpuUsage, (totalCpuUsage / processInfo.length) * 3), 100); } else { // If no process info, generate random CPU usage between 5-30% cpuUsage = 5 + Math.floor(Math.random() * 25); } // Calculate disk usage (average of all disks) let diskUsage = 0; if (diskInfo && diskInfo.length > 0) { diskUsage = diskInfo.reduce((total, disk) => total + disk.percentage, 0) / diskInfo.length; } else { // If no disk info, generate random disk usage between 30-70% diskUsage = 30 + Math.floor(Math.random() * 40); } // Create new arrays with the latest data const timestamps = [...prev.timestamps, now].slice(-MAX_HISTORY_POINTS); const memory = [...prev.memory, memoryPercentage].slice(-MAX_HISTORY_POINTS); const battery = [...prev.battery, batteryLevel].slice(-MAX_HISTORY_POINTS); const network = [...prev.network, networkDownlink].slice(-MAX_HISTORY_POINTS); const cpu = [...prev.cpu, cpuUsage].slice(-MAX_HISTORY_POINTS); const disk = [...prev.disk, diskUsage].slice(-MAX_HISTORY_POINTS); console.log('Updated metrics history:', { timestamps, memory, battery, network, cpu, disk, }); return { timestamps, memory, battery, network, cpu, disk }; }); // Check for memory alerts - only show toast when state changes const currentState = systemMemoryInfo && systemMemoryInfo.percentage > PERFORMANCE_THRESHOLDS.memory.critical ? 'critical-memory' : networkInfo.latency.current > PERFORMANCE_THRESHOLDS.network.latency.critical ? 'critical-network' : batteryInfo && !batteryInfo.charging && batteryInfo.level < PERFORMANCE_THRESHOLDS.battery.critical ? 'critical-battery' : 'normal'; if (currentState === 'critical-memory' && lastAlertState !== 'critical-memory') { const alert: PerformanceAlert = { type: 'error', message: 'Critical system memory usage detected', timestamp: Date.now(), metric: 'memory', threshold: PERFORMANCE_THRESHOLDS.memory.critical, value: systemMemoryInfo?.percentage || 0, }; setAlerts((prev) => { const newAlerts = [...prev, alert]; return newAlerts.slice(-10); }); toast.warning(alert.message, { toastId: 'memory-critical', autoClose: 5000, }); } else if (currentState === 'critical-network' && lastAlertState !== 'critical-network') { const alert: PerformanceAlert = { type: 'warning', message: 'High network latency detected', timestamp: Date.now(), metric: 'network', threshold: PERFORMANCE_THRESHOLDS.network.latency.critical, value: networkInfo.latency.current, }; setAlerts((prev) => { const newAlerts = [...prev, alert]; return newAlerts.slice(-10); }); toast.warning(alert.message, { toastId: 'network-critical', autoClose: 5000, }); } else if (currentState === 'critical-battery' && lastAlertState !== 'critical-battery') { const alert: PerformanceAlert = { type: 'error', message: 'Critical battery level detected', timestamp: Date.now(), metric: 'battery', threshold: PERFORMANCE_THRESHOLDS.battery.critical, value: batteryInfo?.level || 0, }; setAlerts((prev) => { const newAlerts = [...prev, alert]; return newAlerts.slice(-10); }); toast.error(alert.message, { toastId: 'battery-critical', autoClose: 5000, }); } setLastAlertState(currentState); // Then update the environment detection const isCloudflare = !isDevelopment && // Not in development mode ((systemMemoryInfo?.error && systemMemoryInfo.error.includes('not available')) || (processInfo?.[0]?.error && processInfo[0].error.includes('not available')) || (diskInfo?.[0]?.error && diskInfo[0].error.includes('not available'))); // If we detect that we're in a serverless environment, set the flag if (isCloudflare || isServerlessHosting()) { setIsNotSupported(true); } if (isCloudflare) { console.log('Running in Cloudflare environment. System metrics not available.'); } else if (isLocalDevelopment) { console.log('Running in local development environment. Using real or mock system metrics as available.'); } else if (isDevelopment) { console.log('Running in development environment. Using real or mock system metrics as available.'); } else { console.log('Running in production environment. Using real system metrics.'); } } catch (error) { console.error('Failed to update metrics:', error); } }; const getUsageColor = (usage: number): string => { if (usage > 80) { return 'text-red-500'; } if (usage > 50) { return 'text-yellow-500'; } return 'text-gray-500'; }; // Chart rendering function const renderUsageGraph = React.useMemo( () => (data: number[], label: string, color: string, chartRef: React.RefObject>) => { // Ensure we have valid data const validData = data.map((value) => (isNaN(value) ? 0 : value)); // Ensure we have at least 2 data points if (validData.length < 2) { // Add a second point if we only have one if (validData.length === 1) { validData.push(validData[0]); } else { // Add two points if we have none validData.push(0, 0); } } const chartData = { labels: metricsHistory.timestamps.length > 0 ? metricsHistory.timestamps : Array(validData.length) .fill('') .map((_, _i) => new Date().toLocaleTimeString()), datasets: [ { label, data: validData.slice(-MAX_HISTORY_POINTS), borderColor: color, backgroundColor: `${color}33`, // Add slight transparency for fill fill: true, tension: 0.4, pointRadius: 2, // Small points for better UX borderWidth: 2, }, ], }; const options = { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, max: label === 'Network' ? undefined : 100, // Auto-scale for network, 0-100 for others grid: { color: 'rgba(200, 200, 200, 0.1)', drawBorder: false, }, ticks: { maxTicksLimit: 5, callback: (value: any) => { if (label === 'Network') { return `${value} Mbps`; } return `${value}%`; }, }, }, x: { grid: { display: false, }, ticks: { maxTicksLimit: 4, maxRotation: 0, }, }, }, plugins: { legend: { display: false, }, tooltip: { enabled: true, mode: 'index' as const, intersect: false, backgroundColor: 'rgba(0, 0, 0, 0.8)', titleColor: 'white', bodyColor: 'white', borderColor: color, borderWidth: 1, padding: 10, cornerRadius: 4, displayColors: false, callbacks: { title: (tooltipItems: any) => { return tooltipItems[0].label; // Show timestamp }, label: (context: any) => { const value = context.raw; if (label === 'Memory') { return `Memory: ${value.toFixed(1)}%`; } else if (label === 'CPU') { return `CPU: ${value.toFixed(1)}%`; } else if (label === 'Battery') { return `Battery: ${value.toFixed(1)}%`; } else if (label === 'Network') { return `Network: ${value.toFixed(1)} Mbps`; } else if (label === 'Disk') { return `Disk: ${value.toFixed(1)}%`; } return `${label}: ${value.toFixed(1)}`; }, }, }, }, animation: { duration: 300, // Short animation for better UX } as const, elements: { line: { tension: 0.3, }, }, }; return (
); }, [metricsHistory.timestamps], ); // Function to handle sorting const handleSort = (field: SortField) => { if (sortField === field) { // Toggle direction if clicking the same field setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); } else { // Set new field and default to descending setSortField(field); setSortDirection('desc'); } }; // Function to sort processes const getSortedProcesses = () => { if (!metrics.processes) { return []; } return [...metrics.processes].sort((a, b) => { let comparison = 0; switch (sortField) { case 'name': comparison = a.name.localeCompare(b.name); break; case 'pid': comparison = a.pid - b.pid; break; case 'cpu': comparison = a.cpu - b.cpu; break; case 'memory': comparison = a.memory - b.memory; break; } return sortDirection === 'asc' ? comparison : -comparison; }); }; // If we're in an environment where the task manager won't work, show a message if (isNotSupported) { return (

System Monitoring Not Available

System monitoring is not available in serverless environments like Cloudflare Pages, Netlify, or Vercel. These platforms don't provide access to the underlying system resources.

Why is this disabled?
Serverless platforms execute your code in isolated environments without access to the server's operating system metrics like CPU, memory, and disk usage.

System monitoring features will be available when running in:

  • Local development environment
  • Virtual Machines (VMs)
  • Dedicated servers
  • Docker containers (with proper permissions)

{/* Testing controls - only shown in development */} {isDevelopment && (

Testing Controls

These controls are only visible in development mode

)}
); } return (
{/* Summary Header */}
CPU
{(metricsHistory.cpu[metricsHistory.cpu.length - 1] || 0).toFixed(1)}%
Memory
{Math.round(metrics.systemMemory?.percentage || 0)}%
Disk
0 ? metrics.disks.reduce((total, disk) => total + disk.percentage, 0) / metrics.disks.length : 0, ), )} > {metrics.disks && metrics.disks.length > 0 ? Math.round(metrics.disks.reduce((total, disk) => total + disk.percentage, 0) / metrics.disks.length) : 0} %
Network
{metrics.network.downlink.toFixed(1)} Mbps
{/* Memory Usage */}

Memory Usage

{/* System Physical Memory */}
System Memory
Shows your system's physical memory (RAM) usage.
{Math.round(metrics.systemMemory?.percentage || 0)}%
{renderUsageGraph(metricsHistory.memory, 'Memory', '#2563eb', memoryChartRef)}
Used: {formatBytes(metrics.systemMemory?.used || 0)} / {formatBytes(metrics.systemMemory?.total || 0)}
Free: {formatBytes(metrics.systemMemory?.free || 0)}
{/* Swap Memory */} {metrics.systemMemory?.swap && (
Swap Memory
Virtual memory used when physical RAM is full.
{Math.round(metrics.systemMemory.swap.percentage)}%
Used: {formatBytes(metrics.systemMemory.swap.used)} / {formatBytes(metrics.systemMemory.swap.total)}
Free: {formatBytes(metrics.systemMemory.swap.free)}
)}
{/* Disk Usage */}

Disk Usage

{metrics.disks && metrics.disks.length > 0 ? (
System Disk {(metricsHistory.disk[metricsHistory.disk.length - 1] || 0).toFixed(1)}%
{renderUsageGraph(metricsHistory.disk, 'Disk', '#8b5cf6', diskChartRef)} {/* Show only the main system disk (usually the first one) */} {metrics.disks[0] && ( <>
Used: {formatBytes(metrics.disks[0].used)}
Free: {formatBytes(metrics.disks[0].available)}
Total: {formatBytes(metrics.disks[0].size)}
)}
) : (

Disk information is not available

This feature may not be supported in your environment

)}
{/* Process Information */}

Process Information

{metrics.processes && metrics.processes.length > 0 ? ( <> {/* CPU Usage Summary */} {metrics.processes[0].name !== 'Unknown' && (
CPU Usage {(metricsHistory.cpu[metricsHistory.cpu.length - 1] || 0).toFixed(1)}% Total
{metrics.processes.map((process, index) => { return (
); })}
System:{' '} {metrics.processes.reduce((total, proc) => total + (proc.cpu < 10 ? proc.cpu : 0), 0).toFixed(1)}%
User:{' '} {metrics.processes.reduce((total, proc) => total + (proc.cpu >= 10 ? proc.cpu : 0), 0).toFixed(1)} %
Idle: {(100 - (metricsHistory.cpu[metricsHistory.cpu.length - 1] || 0)).toFixed(1)}%
)}
{getSortedProcesses().map((process, index) => ( ))}
handleSort('name')} > Process {sortField === 'name' && (sortDirection === 'asc' ? '↑' : '↓')} handleSort('pid')} > PID {sortField === 'pid' && (sortDirection === 'asc' ? '↑' : '↓')} handleSort('cpu')} > CPU % {sortField === 'cpu' && (sortDirection === 'asc' ? '↑' : '↓')} handleSort('memory')} > Memory {sortField === 'memory' && (sortDirection === 'asc' ? '↑' : '↓')}
{process.name} {process.pid}
{process.cpu.toFixed(1)}%
{/* Calculate approximate MB based on percentage and total system memory */} {metrics.systemMemory ? `${formatBytes(metrics.systemMemory.total * (process.memory / 100))}` : `${process.memory.toFixed(1)}%`}
{metrics.processes[0].error ? (
Error retrieving process information: {metrics.processes[0].error} ) : metrics.processes[0].name === 'Browser' ? (
Showing browser process information. System process information is not available in this environment. ) : ( Showing top {metrics.processes.length} processes by memory usage )}
) : (

Process information is not available

This feature may not be supported in your environment

)}
{/* CPU Usage Graph */}

CPU Usage History

System CPU {(metricsHistory.cpu[metricsHistory.cpu.length - 1] || 0).toFixed(1)}%
{renderUsageGraph(metricsHistory.cpu, 'CPU', '#ef4444', cpuChartRef)}
Average: {(metricsHistory.cpu.reduce((a, b) => a + b, 0) / metricsHistory.cpu.length || 0).toFixed(1)}%
Peak: {Math.max(...metricsHistory.cpu).toFixed(1)}%
{/* Network */}

Network

Connection {metrics.network.downlink.toFixed(1)} Mbps
{renderUsageGraph(metricsHistory.network, 'Network', '#f59e0b', networkChartRef)}
Type: {metrics.network.type} {metrics.network.effectiveType && ` (${metrics.network.effectiveType})`}
Latency: {Math.round(metrics.network.latency.current)}ms (avg: {Math.round(metrics.network.latency.average)}ms)
Min: {Math.round(metrics.network.latency.min)}ms / Max: {Math.round(metrics.network.latency.max)}ms
{metrics.network.uplink && (
Uplink: {metrics.network.uplink.toFixed(1)} Mbps
)}
{/* Battery */} {metrics.battery && (

Battery

Status
{metrics.battery.charging &&
} 20 ? 'text-bolt-elements-textPrimary' : 'text-red-500', )} > {Math.round(metrics.battery.level)}%
{renderUsageGraph(metricsHistory.battery, 'Battery', '#22c55e', batteryChartRef)} {metrics.battery.timeRemaining && metrics.battery.timeRemaining !== Infinity && (
{metrics.battery.charging ? 'Time to full: ' : 'Time remaining: '} {formatTime(metrics.battery.timeRemaining)}
)}
)} {/* Performance */}

Performance

Page Load: {(metrics.performance.pageLoad / 1000).toFixed(2)}s
DOM Ready: {(metrics.performance.domReady / 1000).toFixed(2)}s
TTFB: {(metrics.performance.timing.ttfb / 1000).toFixed(2)}s
Resources: {metrics.performance.resources.total} ({formatBytes(metrics.performance.resources.size)})
{/* Alerts */} {alerts.length > 0 && (
Recent Alerts
{alerts.slice(-5).map((alert, index) => (
{alert.message} {new Date(alert.timestamp).toLocaleTimeString()}
))}
)}
); }; export default React.memo(TaskManagerTab); // Helper function to format bytes const formatBytes = (bytes: number): string => { if (bytes === 0) { return '0 B'; } const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); const value = bytes / Math.pow(k, i); // Format with 2 decimal places for MB and larger units const formattedValue = i >= 2 ? value.toFixed(2) : value.toFixed(0); return `${formattedValue} ${sizes[i]}`; }; // Helper function to format time const formatTime = (seconds: number): string => { if (!isFinite(seconds) || seconds === 0) { return 'Unknown'; } const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); if (hours > 0) { return `${hours}h ${minutes}m`; } return `${minutes}m`; };