diff --git a/frontend/src/components/NetworkDisplay.jsx b/frontend/src/components/NetworkDisplay.jsx
index 6aec4db0d..fd1b2b1c7 100644
--- a/frontend/src/components/NetworkDisplay.jsx
+++ b/frontend/src/components/NetworkDisplay.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState, useCallback } from 'react';
+import React, { useEffect, useState, useCallback, useMemo } from 'react';
import useSocket from '../hooks/useSocket';
import {
Box,
@@ -204,6 +204,32 @@ const StatusIndicator = ({ status }) => {
);
};
+// Add keyboard shortcut tooltip component
+const KeyboardShortcut = ({ shortcut, sx = {} }) => (
+
+ {shortcut}
+
+);
+
const NetworkDisplay = () => {
const {
isConnected,
@@ -597,6 +623,28 @@ const NetworkDisplay = () => {
const downloadColumnRef = React.useRef(null);
const uploadColumnRef = React.useRef(null);
+ // Memoize getSortedAndFilteredData to optimize performance
+ const sortedAndFilteredData = useMemo(
+ () => getSortedAndFilteredData(guestData),
+ [guestData, sortConfig, filters, showStopped, searchTerm, activeSearchTerms, displayMetrics]
+ );
+
+ // Add keyboard shortcut handler for 'F' key to toggle filters
+ useEffect(() => {
+ const handleKeyDown = (e) => {
+ // If 'F' is pressed and no input/textarea is focused, toggle filters
+ if (e.key.toLowerCase() === 'f' &&
+ !['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName) &&
+ !document.activeElement.isContentEditable) {
+ e.preventDefault();
+ setShowFilters(prev => !prev);
+ }
+ };
+
+ document.addEventListener('keydown', handleKeyDown);
+ return () => document.removeEventListener('keydown', handleKeyDown);
+ }, []);
+
if (error) {
return (
{
)}
+
+
+
+
@@ -739,7 +798,14 @@ const NetworkDisplay = () => {
}}>
{/* Filter controls */}
-
+
+ {showFilters ? "Hide filters" : "Show filters"}
+
+ Press F to toggle
+
+ >
+ }>
setShowFilters(!showFilters)}
sx={{
@@ -770,7 +836,7 @@ const NetworkDisplay = () => {
{(Object.values(filters).some(val => val > 0) || searchTerm) ? (
- {`${getSortedAndFilteredData(guestData).length}/${guestData.length}`}
+ {`${sortedAndFilteredData.length}/${guestData.length}`}
) : (
@@ -931,7 +997,11 @@ const NetworkDisplay = () => {
)}
{/* Filter Panel that shows when filters are active */}
-
+
{
-
+
+ val > 0) ? 'primary.main' : 'text.secondary'
+ } sx={{ fontWeight: 500 }}>
+ {Object.values(filters).some(val => val > 0) ?
+ `Showing ${sortedAndFilteredData.length} of ${guestData.length} systems` :
+ ''}
+
val > 0) ? "filled" : "outlined"}
size="small"
color="primary"
- sx={{ height: 28 }}
+ sx={{
+ height: 28,
+ transition: 'all 0.2s ease',
+ fontWeight: Object.values(filters).some(val => val > 0) ? 600 : 400
+ }}
/>
@@ -1165,7 +1246,10 @@ const NetworkDisplay = () => {
>
alpha(theme.palette.grey[50], 0.5),
+ backgroundColor: theme => alpha(theme.palette.grey[50], 0.8),
+ },
+ '& tbody tr:nth-of-type(even)': {
+ backgroundColor: theme => alpha(theme.palette.background.paper, 1),
},
'& tbody tr': {
transition: 'background-color 0.15s ease-in-out',
@@ -1206,7 +1290,19 @@ const NetworkDisplay = () => {
Guest Name
{sortConfig.key === 'name' && (
-
+ alpha(theme.palette.primary.main, 0.1),
+ color: 'primary.main',
+ fontSize: '0.8rem',
+ fontWeight: 'bold'
+ }}>
{sortConfig.direction === 'asc' ? '↑' : '↓'}
)}
@@ -1223,6 +1319,9 @@ const NetworkDisplay = () => {
minHeight: '48px',
position: 'relative',
cursor: 'pointer',
+ borderBottom: theme => filters.cpu > 0 ?
+ `2px solid ${theme.palette.primary.main}` : undefined,
+ color: theme => filters.cpu > 0 ? 'primary.main' : 'inherit',
'&:hover': {
backgroundColor: theme => alpha(theme.palette.primary.main, 0.08)
}
@@ -1232,7 +1331,19 @@ const NetworkDisplay = () => {
CPU
{sortConfig.key === 'cpu' && (
-
+ alpha(theme.palette.primary.main, 0.1),
+ color: 'primary.main',
+ fontSize: '0.8rem',
+ fontWeight: 'bold'
+ }}>
{sortConfig.direction === 'asc' ? '↑' : '↓'}
)}
@@ -1249,6 +1360,9 @@ const NetworkDisplay = () => {
minHeight: '48px',
position: 'relative',
cursor: 'pointer',
+ borderBottom: theme => filters.memory > 0 ?
+ `2px solid ${theme.palette.primary.main}` : undefined,
+ color: theme => filters.memory > 0 ? 'primary.main' : 'inherit',
'&:hover': {
backgroundColor: theme => alpha(theme.palette.primary.main, 0.08)
}
@@ -1258,7 +1372,19 @@ const NetworkDisplay = () => {
Memory
{sortConfig.key === 'memory' && (
-
+ alpha(theme.palette.primary.main, 0.1),
+ color: 'primary.main',
+ fontSize: '0.8rem',
+ fontWeight: 'bold'
+ }}>
{sortConfig.direction === 'asc' ? '↑' : '↓'}
)}
@@ -1275,6 +1401,9 @@ const NetworkDisplay = () => {
minHeight: '48px',
position: 'relative',
cursor: 'pointer',
+ borderBottom: theme => filters.disk > 0 ?
+ `2px solid ${theme.palette.primary.main}` : undefined,
+ color: theme => filters.disk > 0 ? 'primary.main' : 'inherit',
'&:hover': {
backgroundColor: theme => alpha(theme.palette.primary.main, 0.08)
}
@@ -1284,7 +1413,19 @@ const NetworkDisplay = () => {
Disk
{sortConfig.key === 'disk' && (
-
+ alpha(theme.palette.primary.main, 0.1),
+ color: 'primary.main',
+ fontSize: '0.8rem',
+ fontWeight: 'bold'
+ }}>
{sortConfig.direction === 'asc' ? '↑' : '↓'}
)}
@@ -1301,6 +1442,9 @@ const NetworkDisplay = () => {
minHeight: '48px',
position: 'relative',
cursor: 'pointer',
+ borderBottom: theme => filters.download > 0 ?
+ `2px solid ${theme.palette.primary.main}` : undefined,
+ color: theme => filters.download > 0 ? 'primary.main' : 'inherit',
'&:hover': {
backgroundColor: theme => alpha(theme.palette.primary.main, 0.08)
}
@@ -1310,7 +1454,19 @@ const NetworkDisplay = () => {
Download
{sortConfig.key === 'download' && (
-
+ alpha(theme.palette.primary.main, 0.1),
+ color: 'primary.main',
+ fontSize: '0.8rem',
+ fontWeight: 'bold'
+ }}>
{sortConfig.direction === 'asc' ? '↑' : '↓'}
)}
@@ -1327,6 +1483,9 @@ const NetworkDisplay = () => {
minHeight: '48px',
position: 'relative',
cursor: 'pointer',
+ borderBottom: theme => filters.upload > 0 ?
+ `2px solid ${theme.palette.secondary.main}` : undefined,
+ color: theme => filters.upload > 0 ? 'secondary.main' : 'inherit',
'&:hover': {
backgroundColor: theme => alpha(theme.palette.primary.main, 0.08)
}
@@ -1336,7 +1495,19 @@ const NetworkDisplay = () => {
Upload
{sortConfig.key === 'upload' && (
-
+ alpha(theme.palette.secondary.main, 0.1),
+ color: 'secondary.main',
+ fontSize: '0.8rem',
+ fontWeight: 'bold'
+ }}>
{sortConfig.direction === 'asc' ? '↑' : '↓'}
)}
@@ -1346,7 +1517,7 @@ const NetworkDisplay = () => {
{guestData.length > 0 ? (
- getSortedAndFilteredData(guestData)
+ sortedAndFilteredData
.map((guest) => {
const metrics = getMetricsForGuest(guest.id);
const networkMetrics = metrics?.metrics?.network;
@@ -1374,6 +1545,42 @@ const NetworkDisplay = () => {
opacity: isRunning ? 1 : 0.8,
'& > td': { py: 1.5 },
transition: 'all 0.2s ease-in-out',
+ // Highlight rows that match active filters with subtle indicators
+ ...(filters.cpu > 0 && cpuUsage > filters.cpu && {
+ '& td:nth-of-type(2)': {
+ borderLeft: '2px solid',
+ borderLeftColor: theme => alpha(theme.palette.primary.main, 0.4),
+ pl: 1.5 // Add some padding to account for the border
+ }
+ }),
+ ...(filters.memory > 0 && memoryUsage > filters.memory && {
+ '& td:nth-of-type(3)': {
+ borderLeft: '2px solid',
+ borderLeftColor: theme => alpha(theme.palette.primary.main, 0.4),
+ pl: 1.5
+ }
+ }),
+ ...(filters.disk > 0 && diskUsage > filters.disk && {
+ '& td:nth-of-type(4)': {
+ borderLeft: '2px solid',
+ borderLeftColor: theme => alpha(theme.palette.primary.main, 0.4),
+ pl: 1.5
+ }
+ }),
+ ...(filters.download > 0 && networkMetrics?.inRate >= sliderValueToNetworkRate(filters.download) && {
+ '& td:nth-of-type(5)': {
+ borderLeft: '2px solid',
+ borderLeftColor: theme => alpha(theme.palette.primary.main, 0.4),
+ pl: 1.5
+ }
+ }),
+ ...(filters.upload > 0 && networkMetrics?.outRate >= sliderValueToNetworkRate(filters.upload) && {
+ '& td:nth-of-type(6)': {
+ borderLeft: '2px solid',
+ borderLeftColor: theme => alpha(theme.palette.secondary.main, 0.4),
+ pl: 1.5
+ }
+ })
}}>
@@ -1461,7 +1668,7 @@ const NetworkDisplay = () => {
)}
- {guestData.length > 0 && getSortedAndFilteredData(guestData).length === 0 && (
+ {guestData.length > 0 && sortedAndFilteredData.length === 0 && (
@@ -1470,14 +1677,23 @@ const NetworkDisplay = () => {
No Matching Systems
- No systems match the current filters
+ Try adjusting your filters or search terms
-
+
+
+ setShowStopped(true)}
+ sx={{ mt: 1, display: !showStopped ? 'flex' : 'none' }}
+ />
+