diff --git a/frontend/src/components/network/NetworkDisplay.jsx b/frontend/src/components/network/NetworkDisplay.jsx
index bff61acc1..3be6dac60 100644
--- a/frontend/src/components/network/NetworkDisplay.jsx
+++ b/frontend/src/components/network/NetworkDisplay.jsx
@@ -1,9 +1,11 @@
-import React, { useEffect, useMemo } from 'react';
+import React, { useEffect, useMemo, useLayoutEffect } from 'react';
import useSocket from '../../hooks/useSocket';
import useFormattedMetrics from '../../hooks/useFormattedMetrics';
import useMockMetrics from '../../hooks/useMockMetrics';
import { useThemeContext } from '../../context/ThemeContext';
-import { Box, CircularProgress, useTheme, Typography, Button } from '@mui/material';
+import { Box, CircularProgress, useTheme, Typography, Button, IconButton, Badge, Tooltip } from '@mui/material';
+import ViewColumnIcon from '@mui/icons-material/ViewColumn';
+import { createPortal } from 'react-dom';
// Import hooks
import {
@@ -26,6 +28,8 @@ import {
NetworkTable
} from './components';
+import { useSearchContext } from '../../context/SearchContext';
+
const NetworkDisplay = ({ selectedNode = 'all' }) => {
const {
isConnected,
@@ -401,25 +405,32 @@ const NetworkDisplay = ({ selectedNode = 'all' }) => {
requestSort
} = useSortManagement();
- // Use network filters hook
+ // Get search state from context instead of local state
+ const {
+ searchTerm,
+ setSearchTerm,
+ activeSearchTerms,
+ addSearchTerm,
+ removeSearchTerm,
+ clearSearchTerms
+ } = useSearchContext();
+
+ // Use the network filters hook but don't use its search state
const {
filters,
+ setFilters,
showStopped,
setShowStopped,
showFilters,
- searchTerm,
- setSearchTerm,
- activeSearchTerms,
+ setShowFilters,
guestTypeFilter,
setGuestTypeFilter,
- addSearchTerm,
- removeSearchTerm,
+ sliderDragging,
updateFilter,
handleSliderDragStart,
handleSliderDragEnd,
clearFilter,
resetFilters,
- clearSearchTerms,
activeFilterCount
} = useNetworkFilters();
@@ -427,62 +438,200 @@ const NetworkDisplay = ({ selectedNode = 'all' }) => {
const {
filterAnchorEl,
openFilters,
- searchAnchorEl,
- openSearch,
- searchInputRef,
filterButtonRef,
- searchButtonRef,
handleFilterButtonClick,
handleCloseFilterPopover,
- handleSearchButtonClick,
- handleCloseSearchPopover,
closeAllPopovers
} = usePopoverManagement();
- // Use keyboard shortcuts hook
+ // Update resetFilters to also clear search terms from context
+ const handleResetFilters = () => {
+ resetFilters();
+ clearSearchTerms();
+ };
+
+ // Use keyboard shortcuts hook with handleResetFilters instead of resetFilters
useKeyboardShortcuts({
openFilters,
- openSearch,
openColumnMenu,
- resetFilters,
+ resetFilters: handleResetFilters,
closeAllPopovers,
- handleSearchButtonClick,
- searchButtonRef,
- setSearchTerm,
- showNotification,
- searchInputRef
+ showNotification
});
// Use data processing hook
const {
- extractNumericId,
+ processedData,
getNodeName,
- sortedAndFilteredData,
+ extractNumericId,
+ getNodeFilteredGuests,
formatPercentage,
formatNetworkRateForFilter
} = useDataProcessing({
guestData,
- combinedMetrics,
+ nodeData,
sortConfig,
filters,
showStopped,
- activeSearchTerms,
- searchTerm,
selectedNode,
guestTypeFilter,
- nodeData
+ metricsData: combinedMetrics
});
+ // Add debug logging for sorting
+ useEffect(() => {
+ console.log('Current sort configuration:', sortConfig);
+ console.log('Processed data after sorting:', processedData?.length || 0, 'items');
+ }, [sortConfig, processedData]);
+
// Use active filtered columns hook
const activeFilteredColumns = useActiveFilteredColumns({
filters,
- activeSearchTerms,
- searchTerm,
guestTypeFilter,
showStopped,
nodeData
});
+ // Add an effect to listen for search term events
+ useEffect(() => {
+ const handleSearchTermAction = (event) => {
+ const { term, action } = event.detail;
+
+ // Handle status: filters
+ if (term.startsWith('status:')) {
+ const status = term.split(':', 2)[1]?.trim();
+ if (action === 'add') {
+ if (status === 'running') {
+ setShowStopped(false);
+ } else if (status === 'stopped') {
+ setShowStopped(true);
+ }
+ } else if (action === 'remove') {
+ // Reset status filter
+ setShowStopped(null);
+ }
+ }
+
+ // Handle type: filters
+ if (term.startsWith('type:')) {
+ const type = term.split(':', 2)[1]?.trim();
+ if (action === 'add') {
+ if (type === 'vm' || type === 'qemu') {
+ setGuestTypeFilter('vm');
+ } else if (type === 'ct' || type === 'lxc' || type === 'container') {
+ setGuestTypeFilter('ct');
+ }
+ } else if (action === 'remove') {
+ // Reset type filter to "all"
+ setGuestTypeFilter('all');
+ }
+ }
+
+ // Handle node: filters
+ if (term.startsWith('node:')) {
+ const node = term.split(':', 2)[1]?.trim();
+ if (action === 'add') {
+ // Find the node in availableNodes
+ const foundNode = availableNodes.find(n =>
+ n.name.toLowerCase() === node ||
+ n.id.toLowerCase() === node
+ );
+
+ if (foundNode) {
+ // Dispatch a custom event to notify App.jsx about the node change
+ const nodeChangeEvent = new CustomEvent('nodeChange', {
+ detail: { node: foundNode.id }
+ });
+ window.dispatchEvent(nodeChangeEvent);
+ }
+ } else if (action === 'remove') {
+ // Reset node filter to "all"
+ const nodeChangeEvent = new CustomEvent('nodeChange', {
+ detail: { node: 'all' }
+ });
+ window.dispatchEvent(nodeChangeEvent);
+ }
+ }
+ };
+
+ window.addEventListener('searchTermAction', handleSearchTermAction);
+
+ return () => {
+ window.removeEventListener('searchTermAction', handleSearchTermAction);
+ };
+ }, [setShowStopped, setGuestTypeFilter, availableNodes]);
+
+ // State for portal root element
+ const [portalRoot, setPortalRoot] = React.useState(null);
+
+ // Use createPortal to place the column visibility button in the app header
+ useLayoutEffect(() => {
+ // Find the column visibility container in the app header
+ const columnVisibilityContainer = document.getElementById('column-visibility-app-header');
+
+ if (columnVisibilityContainer) {
+ // Create a div for the portal if it doesn't exist yet
+ let portalContainer = document.getElementById('column-visibility-portal');
+ if (!portalContainer) {
+ portalContainer = document.createElement('div');
+ portalContainer.id = 'column-visibility-portal';
+ // Clear previous content and append the portal container
+ columnVisibilityContainer.innerHTML = '';
+ columnVisibilityContainer.appendChild(portalContainer);
+ }
+
+ // Set the portal root for rendering
+ setPortalRoot(portalContainer);
+
+ // Clean up function
+ return () => {
+ setPortalRoot(null);
+ if (portalContainer && portalContainer.parentNode) {
+ portalContainer.parentNode.removeChild(portalContainer);
+ }
+ };
+ }
+ }, []);
+
+ // Render the column visibility button in the portal
+ const renderColumnVisibilityPortal = () => {
+ if (!portalRoot) return null;
+
+ // Count hidden columns for the badge
+ const hiddenColumnsCount = Object.values(columnVisibility).filter(config => !config.visible).length;
+
+ return createPortal(
+ 0 ? `Show hidden columns (${hiddenColumnsCount})` : "Column visibility settings"}>
+ 0 ? 'rgba(255, 255, 255, 0.1)' : 'transparent',
+ color: 'white', // Make sure the icon is visible in the dark app bar
+ '&:hover': {
+ bgcolor: 'rgba(255, 255, 255, 0.2)'
+ }
+ }}
+ >
+
+
+
+
+ ,
+ portalRoot
+ );
+ };
+
// Show loading state if not connected
if (!isConnected) {
if (error || connectionStatus === 'disconnected' || connectionStatus === 'error') {
@@ -524,35 +673,24 @@ const NetworkDisplay = ({ selectedNode = 'all' }) => {
return (
- {/* Header with buttons */}
-
+ {/* Render the column visibility button portal */}
+ {renderColumnVisibilityPortal()}
+
+ {/* Column Menu - Ensure it's still rendered to handle the actual menu */}
+
+ {openColumnMenu && (
+ {
+ if (node && !columnMenuAnchorEl) {
+ handleColumnMenuOpen({ currentTarget: node });
+ }
+ }}
+ />
+ )}
+
{/* Popovers */}
{
updateFilter={updateFilter}
handleSliderDragStart={handleSliderDragStart}
handleSliderDragEnd={handleSliderDragEnd}
- resetFilters={resetFilters}
+ resetFilters={handleResetFilters}
// Formatters
formatPercentage={formatPercentage}
@@ -583,7 +721,7 @@ const NetworkDisplay = ({ selectedNode = 'all' }) => {
columnOrder={columnOrder}
setColumnOrder={setColumnOrder}
activeFilteredColumns={activeFilteredColumns}
- sortedAndFilteredData={sortedAndFilteredData}
+ sortedAndFilteredData={processedData}
guestData={guestData}
metricsData={combinedMetrics}
getNodeName={getNodeName}
@@ -598,9 +736,13 @@ const NetworkDisplay = ({ selectedNode = 'all' }) => {
handleNodeChange={handleNodeChange}
handleStatusChange={handleStatusChange}
handleTypeChange={handleTypeChange}
- activeSearchTerms={activeSearchTerms}
- addSearchTerm={addSearchTerm}
- removeSearchTerm={removeSearchTerm}
+ // Pass filter-related props
+ filters={filters}
+ updateFilter={updateFilter}
+ handleFilterButtonClick={handleFilterButtonClick}
+ filterButtonRef={filterButtonRef}
+ openFilters={openFilters}
+ handleCloseFilterPopover={handleCloseFilterPopover}
/>
{/* Notification Snackbar */}
diff --git a/frontend/src/components/network/NetworkFilters.jsx b/frontend/src/components/network/NetworkFilters.jsx
index c433134bc..0389dd982 100644
--- a/frontend/src/components/network/NetworkFilters.jsx
+++ b/frontend/src/components/network/NetworkFilters.jsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import {
Box,
Typography,
@@ -20,7 +20,8 @@ import {
ToggleButtonGroup,
ToggleButton,
Divider,
- Button
+ Button,
+ useTheme
} from '@mui/material';
import FilterAltIcon from '@mui/icons-material/FilterAlt';
import FilterAltOffIcon from '@mui/icons-material/FilterAltOff';
@@ -33,7 +34,7 @@ import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { formatPercentage, formatNetworkRateForFilter, sliderValueToNetworkRate } from '../../utils/formatters';
import { KeyboardShortcut } from './UIComponents';
-import { useTheme } from '@mui/material/styles';
+import { useSearchContext } from '../../context/SearchContext';
const NetworkFilters = ({
filters,
@@ -54,9 +55,20 @@ const NetworkFilters = ({
searchInputRef,
guestTypeFilter,
setGuestTypeFilter,
- handleClose
+ handleClose,
+ formatPercentage,
+ formatNetworkRateForFilter
}) => {
const theme = useTheme();
+ // Get clearSearchTerms from context
+ const { clearSearchTerms } = useSearchContext();
+
+ // Handle resetting all filters including search terms
+ const handleResetAllFilters = () => {
+ resetFilters();
+ clearSearchTerms();
+ if (handleClose) handleClose();
+ };
// Handle search input submission
const handleSearchSubmit = (e) => {
@@ -116,7 +128,14 @@ const NetworkFilters = ({
setSearchTerm(e.target.value)}
+ onChange={(e) => {
+ // Log the current input value for debugging
+ console.log('NetworkFilters input onChange:', e.target.value, 'Previous value:', searchTerm);
+
+ // Directly set the search term to the current input value
+ // This should ensure the value is properly updated
+ setSearchTerm(e.target.value);
+ }}
sx={{ flex: 1 }}
inputRef={searchInputRef}
inputProps={{ 'aria-label': 'search systems' }}
@@ -125,7 +144,10 @@ const NetworkFilters = ({
setSearchTerm('')}
+ onClick={() => {
+ setSearchTerm('');
+ searchInputRef.current?.focus();
+ }}
sx={{ p: 0.5 }}
>
@@ -237,16 +259,22 @@ const NetworkFilters = ({
{/* CPU Filter */}
-
+ 0 ? alpha(theme.palette.primary.main, 0.08) : 'transparent'
+ }}>
-
+ 0 ? "primary" : "text.secondary"} sx={{ fontWeight: filters.cpu > 0 ? 500 : 400 }}>
CPU Usage
-
+ 0 ? "primary" : "text.secondary"} sx={{ fontWeight: filters.cpu > 0 ? 500 : 400 }}>
{formatPercentage(filters.cpu)}+
updateFilter('cpu', newValue)}
onMouseDown={() => handleSliderDragStart('cpu')}
@@ -257,20 +285,32 @@ const NetworkFilters = ({
min={0}
max={100}
size="small"
+ sx={{
+ color: filters.cpu > 0 ? theme.palette.primary.main : undefined,
+ '& .MuiSlider-thumb': {
+ boxShadow: filters.cpu > 0 ? `0 0 0 8px ${alpha(theme.palette.primary.main, 0.16)}` : undefined,
+ }
+ }}
/>
{/* Memory Filter */}
-
+ 0 ? alpha(theme.palette.primary.main, 0.08) : 'transparent'
+ }}>
-
+ 0 ? "primary" : "text.secondary"} sx={{ fontWeight: filters.memory > 0 ? 500 : 400 }}>
Memory Usage
-
+ 0 ? "primary" : "text.secondary"} sx={{ fontWeight: filters.memory > 0 ? 500 : 400 }}>
{formatPercentage(filters.memory)}+
updateFilter('memory', newValue)}
onMouseDown={() => handleSliderDragStart('memory')}
@@ -281,20 +321,32 @@ const NetworkFilters = ({
min={0}
max={100}
size="small"
+ sx={{
+ color: filters.memory > 0 ? theme.palette.primary.main : undefined,
+ '& .MuiSlider-thumb': {
+ boxShadow: filters.memory > 0 ? `0 0 0 8px ${alpha(theme.palette.primary.main, 0.16)}` : undefined,
+ }
+ }}
/>
{/* Disk Filter */}
-
+ 0 ? alpha(theme.palette.primary.main, 0.08) : 'transparent'
+ }}>
-
+ 0 ? "primary" : "text.secondary"} sx={{ fontWeight: filters.disk > 0 ? 500 : 400 }}>
Disk Usage
-
+ 0 ? "primary" : "text.secondary"} sx={{ fontWeight: filters.disk > 0 ? 500 : 400 }}>
{formatPercentage(filters.disk)}+
updateFilter('disk', newValue)}
onMouseDown={() => handleSliderDragStart('disk')}
@@ -305,20 +357,32 @@ const NetworkFilters = ({
min={0}
max={100}
size="small"
+ sx={{
+ color: filters.disk > 0 ? theme.palette.primary.main : undefined,
+ '& .MuiSlider-thumb': {
+ boxShadow: filters.disk > 0 ? `0 0 0 8px ${alpha(theme.palette.primary.main, 0.16)}` : undefined,
+ }
+ }}
/>
- {/* Network Download Filter */}
-
+ {/* Download Filter */}
+ 0 ? alpha(theme.palette.primary.main, 0.08) : 'transparent'
+ }}>
-
+ 0 ? "primary" : "text.secondary"} sx={{ fontWeight: filters.download > 0 ? 500 : 400 }}>
Download Rate
-
+ 0 ? "primary" : "text.secondary"} sx={{ fontWeight: filters.download > 0 ? 500 : 400 }}>
{formatNetworkRateForFilter(filters.download)}+
updateFilter('download', newValue)}
onMouseDown={() => handleSliderDragStart('download')}
@@ -327,22 +391,34 @@ const NetworkFilters = ({
valueLabelDisplay="auto"
valueLabelFormat={formatNetworkRateForFilter}
min={0}
- max={100}
+ max={100000}
size="small"
+ sx={{
+ color: filters.download > 0 ? theme.palette.primary.main : undefined,
+ '& .MuiSlider-thumb': {
+ boxShadow: filters.download > 0 ? `0 0 0 8px ${alpha(theme.palette.primary.main, 0.16)}` : undefined,
+ }
+ }}
/>
- {/* Network Upload Filter */}
-
+ {/* Upload Filter */}
+ 0 ? alpha(theme.palette.primary.main, 0.08) : 'transparent'
+ }}>
-
+ 0 ? "primary" : "text.secondary"} sx={{ fontWeight: filters.upload > 0 ? 500 : 400 }}>
Upload Rate
-
+ 0 ? "primary" : "text.secondary"} sx={{ fontWeight: filters.upload > 0 ? 500 : 400 }}>
{formatNetworkRateForFilter(filters.upload)}+
updateFilter('upload', newValue)}
onMouseDown={() => handleSliderDragStart('upload')}
@@ -351,8 +427,14 @@ const NetworkFilters = ({
valueLabelDisplay="auto"
valueLabelFormat={formatNetworkRateForFilter}
min={0}
- max={100}
+ max={100000}
size="small"
+ sx={{
+ color: filters.upload > 0 ? theme.palette.primary.main : undefined,
+ '& .MuiSlider-thumb': {
+ boxShadow: filters.upload > 0 ? `0 0 0 8px ${alpha(theme.palette.primary.main, 0.16)}` : undefined,
+ }
+ }}
/>
@@ -365,10 +447,7 @@ const NetworkFilters = ({
size="small"
fullWidth
variant="outlined"
- onClick={() => {
- resetFilters();
- handleClose && handleClose();
- }}
+ onClick={handleResetAllFilters}
startIcon={}
>
Reset All Filters
diff --git a/frontend/src/components/network/NetworkTableBody.jsx b/frontend/src/components/network/NetworkTableBody.jsx
index 0fff3c323..c82bc39d3 100644
--- a/frontend/src/components/network/NetworkTableBody.jsx
+++ b/frontend/src/components/network/NetworkTableBody.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useMemo } from 'react';
import {
TableBody,
TableRow,
@@ -6,12 +6,16 @@ import {
Box,
Typography,
Chip,
- Button
+ Button,
+ Divider,
+ alpha,
+ useTheme
} from '@mui/material';
import NetworkCheckIcon from '@mui/icons-material/NetworkCheck';
-import FilterAltIcon from '@mui/icons-material/FilterAlt';
+import FilterAltIcon from '@mui/icons-material/FilterList';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import NetworkTableRow from './NetworkTableRow';
+import { getNodeTextColor } from '../../utils/colorUtils';
const NetworkTableBody = ({
sortedAndFilteredData,
@@ -27,8 +31,12 @@ const NetworkTableBody = ({
resetColumnVisibility,
forceUpdateCounter,
columnOrder,
- activeFilteredColumns = {}
+ activeFilteredColumns = {},
+ thresholdColumn,
+ thresholdValue
}) => {
+ const theme = useTheme();
+
// Check if any columns are visible
const hasVisibleColumns = Object.values(columnVisibility).some(col => col.visible);
@@ -39,6 +47,33 @@ const NetworkTableBody = ({
}
}, [forceUpdateCounter]);
+ // Group guests by node
+ const groupedGuests = useMemo(() => {
+ if (!sortedAndFilteredData || !Array.isArray(sortedAndFilteredData)) {
+ return [];
+ }
+
+ // Use a Map to preserve insertion order
+ const nodeGroups = new Map();
+
+ // Group guests by node
+ sortedAndFilteredData.forEach(guest => {
+ const nodeName = getNodeName(guest?.node) || 'Unknown';
+
+ if (!nodeGroups.has(nodeName)) {
+ nodeGroups.set(nodeName, []);
+ }
+
+ nodeGroups.get(nodeName).push(guest);
+ });
+
+ // Convert Map to array of objects
+ return Array.from(nodeGroups.entries()).map(([nodeName, guests]) => ({
+ nodeName,
+ guests
+ }));
+ }, [sortedAndFilteredData, getNodeName]);
+
// If no columns are visible, show a message
if (!hasVisibleColumns) {
return (
@@ -46,19 +81,47 @@ const NetworkTableBody = ({
-
-
- No Columns Visible
+
+
+ No columns are visible
-
- All columns are currently hidden. Click the button below to reset column visibility.
+
+ Use the column visibility button to show some columns
}
>
- Reset Column Visibility
+ Reset Columns
+
+
+
+
+
+ );
+ }
+
+ // If no data matches the filters, show a message
+ if (sortedAndFilteredData.length === 0) {
+ return (
+
+
+
+
+
+
+ No matching guests found
+
+
+ Try adjusting your filters or search terms
+
+ }
+ >
+ Reset Filters
@@ -67,82 +130,85 @@ const NetworkTableBody = ({
);
}
- // If no data is available, show a message
- if (!sortedAndFilteredData || sortedAndFilteredData.length === 0) {
- return (
-
-
-
-
-
-
- No Systems Found
-
-
- {!showStopped ? (
- <>
- No running systems match your current filters.
- {guestData && guestData.length > 0 && (
- <>
-
- Try showing stopped systems or adjusting your filters.
- >
- )}
- >
- ) : (
- <>
- No stopped systems match your current filters.
- {guestData && guestData.length > 0 && (
- <>
-
- Try showing running systems or adjusting your filters.
- >
- )}
- >
- )}
-
-
- {guestData && (
-
- )}
- }
- size="small"
- >
- Reset Filters
-
-
-
-
-
-
- );
- }
-
+ // Calculate visible columns for node header colspan
+ const visibleColumnCount = Object.values(columnVisibility).filter(col => col.visible).length;
+
+ // Render the table with data grouped by node
return (
-
- {Array.isArray(sortedAndFilteredData) && sortedAndFilteredData.map((guest) => (
- guest && (
- 'Unknown')}
- extractNumericId={extractNumericId || ((id) => id)}
- columnOrder={columnOrder || []}
- activeFilteredColumns={activeFilteredColumns}
- />
- )
- ))}
+
+ {groupedGuests.map((nodeGroup, groupIndex) => {
+ const { nodeName, guests } = nodeGroup;
+
+ return (
+
+ {/* Node Header/Divider */}
+ alpha(theme.palette.primary.main, 0.05),
+ '&:hover': {
+ bgcolor: theme => alpha(theme.palette.primary.main, 0.08)
+ }
+ }}
+ >
+ 0 ? '2px solid' : 'none',
+ borderColor: theme => alpha(theme.palette.divider, 0.8)
+ }}
+ >
+
+ {
+ const color = getNodeTextColor(nodeName, theme.palette.mode);
+ return color;
+ }
+ }}
+ >
+ NODE: {nodeName}
+
+
+ {guests.length} {guests.length === 1 ? 'guest' : 'guests'}
+
+
+
+
+
+ {/* Guest Rows */}
+ {guests.map(guest => (
+
+ ))}
+
+ );
+ })}
);
};
diff --git a/frontend/src/components/network/UIComponents.jsx b/frontend/src/components/network/UIComponents.jsx
index 985a3cb5f..364aac7ac 100644
--- a/frontend/src/components/network/UIComponents.jsx
+++ b/frontend/src/components/network/UIComponents.jsx
@@ -222,4 +222,59 @@ export const KeyboardShortcut = ({ shortcut, sx = {} }) => (
>
{shortcut}
-);
\ No newline at end of file
+);
+
+// Add a new HighlightedText component for search term highlighting
+export const HighlightedText = ({ text, searchTerms = [], variant = "body2", sx = {}, ...props }) => {
+ const theme = useTheme();
+
+ if (!text || !searchTerms || searchTerms.length === 0) {
+ return {text};
+ }
+
+ // Convert to string to handle numeric values
+ const textStr = String(text || '');
+
+ // Prepare regular expression for matching - case insensitive
+ // Escape special regex characters in search terms
+ const escapedTerms = searchTerms.map(term =>
+ String(term).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ );
+
+ // Create regex pattern from all terms
+ const pattern = new RegExp(`(${escapedTerms.join('|')})`, 'gi');
+
+ // Split text by matches
+ const parts = textStr.split(pattern);
+
+ // Skip highlighting if no matches
+ if (parts.length <= 1) {
+ return {text};
+ }
+
+ return (
+
+ {parts.map((part, i) => {
+ // Check if this part matches any search term (case insensitive)
+ const isMatch = searchTerms.some(term =>
+ part.toLowerCase() === String(term).toLowerCase()
+ );
+
+ return isMatch ? (
+
+ {part}
+
+ ) : part;
+ })}
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/network/components/NetworkHeader.jsx b/frontend/src/components/network/components/NetworkHeader.jsx
index 223045dc3..c65f7300b 100644
--- a/frontend/src/components/network/components/NetworkHeader.jsx
+++ b/frontend/src/components/network/components/NetworkHeader.jsx
@@ -1,118 +1,29 @@
import React from 'react';
import {
- Box,
- Typography,
- IconButton,
- Badge
+ Box
} from '@mui/material';
-import ViewColumnIcon from '@mui/icons-material/ViewColumn';
-import SearchIcon from '@mui/icons-material/Search';
-import TuneIcon from '@mui/icons-material/Tune';
-import { DEFAULT_COLUMN_CONFIG } from '../../../constants/networkConstants';
const NetworkHeader = ({
- openColumnMenu,
- openSearch,
openFilters,
- activeSearchTerms,
filters,
- columnVisibility,
- handleColumnMenuOpen,
- handleSearchButtonClick,
handleFilterButtonClick,
- searchButtonRef,
filterButtonRef
}) => {
return (
-
-
- {/* Column visibility button */}
-
- col.visible).length}
- color="primary"
- invisible={Object.values(columnVisibility).filter(col => col.visible).length === Object.keys(DEFAULT_COLUMN_CONFIG).length}
- >
-
-
-
-
- {/* Search button */}
-
-
-
-
-
-
- {/* Resource thresholds button */}
-
- 0 ? 1 : 0) +
- (filters.memory > 0 ? 1 : 0) +
- (filters.disk > 0 ? 1 : 0) +
- (filters.download > 0 ? 1 : 0) +
- (filters.upload > 0 ? 1 : 0)
- }
- color="primary"
- invisible={
- filters.cpu === 0 &&
- filters.memory === 0 &&
- filters.disk === 0 &&
- filters.download === 0 &&
- filters.upload === 0
- }
- >
-
-
-
+
+ {/* Left side - Intentionally empty, removed Dashboard title */}
+
+
+ {/* Right side - Actions - Column visibility button has been moved to the main app header */}
+
+ {/* Additional buttons can be added here if needed */}
);
diff --git a/frontend/src/components/network/components/NetworkPopovers.jsx b/frontend/src/components/network/components/NetworkPopovers.jsx
index dd20fba92..7a0b6f939 100644
--- a/frontend/src/components/network/components/NetworkPopovers.jsx
+++ b/frontend/src/components/network/components/NetworkPopovers.jsx
@@ -4,35 +4,14 @@ import {
Paper,
Box,
Typography,
- TextField,
IconButton,
- Chip,
Button,
- ToggleButtonGroup,
- ToggleButton,
- MenuItem,
- Switch,
- Slider,
- Divider,
- Radio
+ Slider
} from '@mui/material';
-import SearchIcon from '@mui/icons-material/Search';
-import ClearIcon from '@mui/icons-material/Clear';
import FilterAltOffIcon from '@mui/icons-material/FilterAltOff';
+import { useSearchContext } from '../../../context/SearchContext';
const NetworkPopovers = ({
- // Search popover props
- searchAnchorEl,
- openSearch,
- handleCloseSearchPopover,
- searchTerm,
- setSearchTerm,
- activeSearchTerms,
- addSearchTerm,
- removeSearchTerm,
- searchInputRef,
- clearSearchTerms,
-
// Filter popover props
filterAnchorEl,
openFilters,
@@ -47,196 +26,19 @@ const NetworkPopovers = ({
formatPercentage,
formatNetworkRateForFilter
}) => {
+ // Get clearSearchTerms from context to ensure search terms are cleared when filters are reset
+ const { clearSearchTerms } = useSearchContext();
+
+ // Handle resetting all filters including search terms
+ const handleResetAllFilters = () => {
+ resetFilters();
+ clearSearchTerms();
+ handleCloseFilterPopover();
+ };
+
return (
<>
- {/* Search popover */}
-
-
- {/* Resource thresholds popover */}
+ {/* Filter popover */}