mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-10-09 15:58:34 +00:00
95bc7f018c
Add real-time monitoring of Linux server resources in the terminal statusbar: - CPU usage with per-core breakdown in hover popup - Memory usage with htop-style colored bar (Used/Buffers/Cache/Free) - Top 10 processes by memory consumption - Disk usage for all mounted partitions - Network speed (RX/TX) with per-interface details Features: - Only enabled for Linux servers (detected automatically) - Configurable refresh interval (default 5 seconds) - Toggle in Settings > Terminal tab - Hover-triggered popups using Radix UI HoverCard - BusyBox compatibility with fallback commands Closes #108 Co-Authored-By: Claude <noreply@anthropic.com>
202 lines
6.0 KiB
TypeScript
202 lines
6.0 KiB
TypeScript
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
import { netcattyBridge } from '../../../infrastructure/services/netcattyBridge';
|
|
|
|
export interface DiskInfo {
|
|
mountPoint: string;
|
|
used: number; // Used in GB
|
|
total: number; // Total in GB
|
|
percent: number; // Usage percentage
|
|
}
|
|
|
|
export interface NetInterfaceInfo {
|
|
name: string; // Interface name (e.g., eth0, ens33)
|
|
rxBytes: number; // Total received bytes
|
|
txBytes: number; // Total transmitted bytes
|
|
rxSpeed: number; // Receive speed (bytes/sec)
|
|
txSpeed: number; // Transmit speed (bytes/sec)
|
|
}
|
|
|
|
export interface ProcessInfo {
|
|
pid: string;
|
|
memPercent: number;
|
|
command: string;
|
|
}
|
|
|
|
export interface ServerStats {
|
|
cpu: number | null; // CPU usage percentage (0-100)
|
|
cpuCores: number | null; // Number of CPU cores
|
|
cpuPerCore: number[]; // Per-core CPU usage array
|
|
memTotal: number | null; // Total memory in MB
|
|
memUsed: number | null; // Used memory in MB (excluding buffers/cache)
|
|
memFree: number | null; // Free memory in MB
|
|
memBuffers: number | null; // Buffers in MB
|
|
memCached: number | null; // Cached in MB
|
|
topProcesses: ProcessInfo[]; // Top 10 processes by memory
|
|
diskPercent: number | null; // Disk usage percentage for root partition
|
|
diskUsed: number | null; // Disk used in GB
|
|
diskTotal: number | null; // Total disk in GB
|
|
disks: DiskInfo[]; // All mounted disks
|
|
netRxSpeed: number; // Total network receive speed (bytes/sec)
|
|
netTxSpeed: number; // Total network transmit speed (bytes/sec)
|
|
netInterfaces: NetInterfaceInfo[]; // Per-interface network stats
|
|
lastUpdated: number | null; // Timestamp of last successful update
|
|
}
|
|
|
|
interface UseServerStatsOptions {
|
|
sessionId: string;
|
|
enabled: boolean; // Whether stats collection is enabled (from settings)
|
|
refreshInterval: number; // Refresh interval in seconds
|
|
isLinux: boolean; // Only collect stats for Linux servers
|
|
isConnected: boolean; // Only collect when connected
|
|
}
|
|
|
|
export function useServerStats({
|
|
sessionId,
|
|
enabled,
|
|
refreshInterval,
|
|
isLinux,
|
|
isConnected,
|
|
}: UseServerStatsOptions) {
|
|
const [stats, setStats] = useState<ServerStats>({
|
|
cpu: null,
|
|
cpuCores: null,
|
|
cpuPerCore: [],
|
|
memTotal: null,
|
|
memUsed: null,
|
|
memFree: null,
|
|
memBuffers: null,
|
|
memCached: null,
|
|
topProcesses: [],
|
|
diskPercent: null,
|
|
diskUsed: null,
|
|
diskTotal: null,
|
|
disks: [],
|
|
netRxSpeed: 0,
|
|
netTxSpeed: 0,
|
|
netInterfaces: [],
|
|
lastUpdated: null,
|
|
});
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
const isMountedRef = useRef(true);
|
|
|
|
const fetchStats = useCallback(async () => {
|
|
if (!enabled || !isLinux || !isConnected || !sessionId) {
|
|
return;
|
|
}
|
|
|
|
const bridge = netcattyBridge.get();
|
|
if (!bridge?.getServerStats) {
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const result = await bridge.getServerStats(sessionId);
|
|
|
|
if (!isMountedRef.current) return;
|
|
|
|
if (result.success && result.stats) {
|
|
setStats({
|
|
cpu: result.stats.cpu,
|
|
cpuCores: result.stats.cpuCores,
|
|
cpuPerCore: result.stats.cpuPerCore || [],
|
|
memTotal: result.stats.memTotal,
|
|
memUsed: result.stats.memUsed,
|
|
memFree: result.stats.memFree,
|
|
memBuffers: result.stats.memBuffers,
|
|
memCached: result.stats.memCached,
|
|
topProcesses: result.stats.topProcesses || [],
|
|
diskPercent: result.stats.diskPercent,
|
|
diskUsed: result.stats.diskUsed,
|
|
diskTotal: result.stats.diskTotal,
|
|
disks: result.stats.disks || [],
|
|
netRxSpeed: result.stats.netRxSpeed || 0,
|
|
netTxSpeed: result.stats.netTxSpeed || 0,
|
|
netInterfaces: result.stats.netInterfaces || [],
|
|
lastUpdated: Date.now(),
|
|
});
|
|
} else if (result.error) {
|
|
setError(result.error);
|
|
}
|
|
} catch (err) {
|
|
if (isMountedRef.current) {
|
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
}
|
|
} finally {
|
|
if (isMountedRef.current) {
|
|
setIsLoading(false);
|
|
}
|
|
}
|
|
}, [sessionId, enabled, isLinux, isConnected]);
|
|
|
|
// Initial fetch and periodic refresh
|
|
useEffect(() => {
|
|
isMountedRef.current = true;
|
|
|
|
// Clear any existing interval
|
|
if (intervalRef.current) {
|
|
clearInterval(intervalRef.current);
|
|
intervalRef.current = null;
|
|
}
|
|
|
|
// Don't run if not enabled or not a Linux system
|
|
if (!enabled || !isLinux || !isConnected) {
|
|
// Reset stats when disabled or not connected
|
|
setStats({
|
|
cpu: null,
|
|
cpuCores: null,
|
|
cpuPerCore: [],
|
|
memTotal: null,
|
|
memUsed: null,
|
|
memFree: null,
|
|
memBuffers: null,
|
|
memCached: null,
|
|
topProcesses: [],
|
|
diskPercent: null,
|
|
diskUsed: null,
|
|
diskTotal: null,
|
|
disks: [],
|
|
netRxSpeed: 0,
|
|
netTxSpeed: 0,
|
|
netInterfaces: [],
|
|
lastUpdated: null,
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Initial fetch with a small delay to let the connection stabilize
|
|
const initialTimer = setTimeout(() => {
|
|
fetchStats();
|
|
}, 2000);
|
|
|
|
// Set up periodic refresh
|
|
const intervalMs = Math.max(5, refreshInterval) * 1000; // Minimum 5 seconds
|
|
intervalRef.current = setInterval(fetchStats, intervalMs);
|
|
|
|
return () => {
|
|
isMountedRef.current = false;
|
|
clearTimeout(initialTimer);
|
|
if (intervalRef.current) {
|
|
clearInterval(intervalRef.current);
|
|
intervalRef.current = null;
|
|
}
|
|
};
|
|
}, [enabled, isLinux, isConnected, refreshInterval, fetchStats]);
|
|
|
|
// Manual refresh function
|
|
const refresh = useCallback(() => {
|
|
fetchStats();
|
|
}, [fetchStats]);
|
|
|
|
return {
|
|
stats,
|
|
isLoading,
|
|
error,
|
|
refresh,
|
|
};
|
|
}
|