diff --git a/frontend/src/components/network/NetworkTableHeader.jsx b/frontend/src/components/network/NetworkTableHeader.jsx
index 1153f9022..382774016 100644
--- a/frontend/src/components/network/NetworkTableHeader.jsx
+++ b/frontend/src/components/network/NetworkTableHeader.jsx
@@ -1,4 +1,4 @@
-import React, { useMemo } from 'react';
+import React, { useMemo, useState, useEffect, useCallback } from 'react';
import {
TableHead,
TableRow,
@@ -19,10 +19,15 @@ import {
ListItemText,
Select,
FormControl,
- Popover
+ Popover,
+ Fade,
+ Grow,
+ Slider
} from '@mui/material';
import ViewColumnIcon from '@mui/icons-material/ViewColumn';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
+import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
+import TuneIcon from '@mui/icons-material/Tune';
import CheckIcon from '@mui/icons-material/Check';
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
import CircleIcon from '@mui/icons-material/Circle';
@@ -33,14 +38,22 @@ import ViewListIcon from '@mui/icons-material/ViewList';
import DnsIcon from '@mui/icons-material/Dns';
import ComputerIcon from '@mui/icons-material/Computer';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
+import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import FilterListIcon from '@mui/icons-material/FilterList';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import StopIcon from '@mui/icons-material/Stop';
import AllInclusiveIcon from '@mui/icons-material/AllInclusive';
import StorageIcon from '@mui/icons-material/Storage';
import SearchIcon from '@mui/icons-material/Search';
+import CloseIcon from '@mui/icons-material/Close';
+import MoreVertIcon from '@mui/icons-material/MoreVert';
import { DEFAULT_COLUMN_CONFIG } from '../../constants/networkConstants';
import { calculateDynamicColumnWidths } from '../../utils/networkUtils';
+import { useSearchContext } from '../../context/SearchContext';
+import { useSnackbar } from 'notistack';
+
+// Modify the Resizer component to be a no-op since we're removing manual resizing
+const Resizer = () => null;
const NetworkTableHeader = ({
sortConfig,
@@ -66,11 +79,104 @@ const NetworkTableHeader = ({
handleNodeChange = () => {},
handleStatusChange = () => {},
handleTypeChange = () => {},
- activeSearchTerms = [],
- addSearchTerm = () => {},
- removeSearchTerm = () => {}
+ filters = {},
+ updateFilter = () => {},
+ handleFilterButtonClick = () => {},
+ filterButtonRef = null,
+ openFilters = false,
+ handleCloseFilterPopover = () => {},
}) => {
const theme = useTheme();
+ const { enqueueSnackbar } = useSnackbar();
+
+ // Remove resize state and localStorage logic since we're eliminating manual resizing
+ // Instead, we'll rely on intelligent auto-sizing
+
+ // Define column groups for sizing strategy
+ const fixedNarrowColumns = ['type', 'id', 'status']; // Very narrow columns
+ const fixedWidthColumns = ['download', 'upload', 'uptime']; // Fixed width for network stats
+ const autoSizeColumns = ['name', 'node']; // Auto-size to content
+ const flexibleEqualColumns = ['cpu', 'memory', 'disk']; // Equal width with progress bars
+
+ // Calculate visible columns and their groups
+ const getVisibleColumnGroups = useCallback(() => {
+ if (!columnOrder || !Array.isArray(columnOrder) || columnOrder.length === 0) {
+ return { fixedNarrow: [], fixedWidth: [], autoSize: [], flexibleEqual: [] };
+ }
+
+ const visibleColumnIds = columnOrder.filter(id => columnVisibility[id]?.visible);
+
+ return {
+ fixedNarrow: visibleColumnIds.filter(id => fixedNarrowColumns.includes(id)),
+ fixedWidth: visibleColumnIds.filter(id => fixedWidthColumns.includes(id)),
+ autoSize: visibleColumnIds.filter(id => autoSizeColumns.includes(id)),
+ flexibleEqual: visibleColumnIds.filter(id => flexibleEqualColumns.includes(id))
+ };
+ }, [columnOrder, columnVisibility]);
+
+ // Intelligent column width calculation
+ const getColumnWidth = useCallback((columnId) => {
+ // Define fixed widths for specific columns
+ const fixedWidths = {
+ type: '50px', // Just "VM" or "CT"
+ id: '60px', // Just numeric IDs
+ status: '50px', // Just the status circle
+ download: '90px', // Network rates
+ upload: '90px', // Network rates
+ uptime: '90px' // Time display
+ };
+
+ // If column has a fixed width, return it
+ if (fixedWidths[columnId]) {
+ return fixedWidths[columnId];
+ }
+
+ // Get all visible column groups
+ const columnGroups = getVisibleColumnGroups();
+
+ // For auto-sized columns, use different strategies for name vs node
+ if (autoSizeColumns.includes(columnId)) {
+ // Use max-content for node to fit content more closely
+ if (columnId === 'node') {
+ return 'max-content';
+ }
+ // Use auto for name which can be longer
+ return 'auto';
+ }
+
+ // For flexible equal columns (cpu, memory, disk), calculate percentage
+ if (flexibleEqualColumns.includes(columnId)) {
+ // Calculate how many flexible columns are visible
+ const visibleFlexColumns = columnGroups.flexibleEqual.length;
+
+ if (visibleFlexColumns > 0) {
+ // Calculate total width taken by fixed columns
+ // (This is an approximation since auto columns are unknown)
+ const approximateFixedWidth =
+ (columnGroups.fixedNarrow.length * 60) +
+ (columnGroups.fixedWidth.length * 90) +
+ (columnGroups.autoSize.length * 120); // Rough estimate for auto columns
+
+ // Approximate remaining percentage for flexible columns
+ // Assuming table width is roughly 1000px (this is just a heuristic)
+ const remainingPercentage = Math.max(10, (1000 - approximateFixedWidth) / 10); // in %
+
+ // Divide equally among visible flexible columns
+ return `${Math.floor(remainingPercentage / visibleFlexColumns)}%`;
+ }
+
+ // Fallback if something goes wrong
+ return '20%';
+ }
+
+ // Default fallback
+ return 'auto';
+ }, [getVisibleColumnGroups]);
+
+ // Helper to check column type
+ const isFlexibleColumn = (columnId) => {
+ return flexibleEqualColumns.includes(columnId);
+ };
const [nodeMenuAnchorEl, setNodeMenuAnchorEl] = React.useState(null);
const openNodeMenu = Boolean(nodeMenuAnchorEl);
@@ -81,10 +187,8 @@ const NetworkTableHeader = ({
const [typeMenuAnchorEl, setTypeMenuAnchorEl] = React.useState(null);
const openTypeMenu = Boolean(typeMenuAnchorEl);
- const handleNodeColumnClick = (event) => {
- event.stopPropagation();
- setNodeMenuAnchorEl(event.currentTarget);
- };
+ // Get search state from context
+ const { activeSearchTerms, addSearchTerm, removeSearchTerm } = useSearchContext();
const handleNodeMenuClose = () => {
setNodeMenuAnchorEl(null);
@@ -110,7 +214,7 @@ const NetworkTableHeader = ({
// Add the new node filter as a search term
addSearchTerm(`node:${nodeName}`);
- } else {
+ } else {
// Remove any node: filters when "all" is selected
const existingNodeTerms = activeSearchTerms.filter(term =>
term.toLowerCase().startsWith('node:')
@@ -122,11 +226,6 @@ const NetworkTableHeader = ({
handleNodeMenuClose();
};
-
- const handleStatusColumnClick = (event) => {
- event.stopPropagation();
- setStatusMenuAnchorEl(event.currentTarget);
- };
const handleStatusMenuClose = () => {
setStatusMenuAnchorEl(null);
@@ -158,11 +257,6 @@ const NetworkTableHeader = ({
handleStatusMenuClose();
};
-
- const handleTypeColumnClick = (event) => {
- event.stopPropagation();
- setTypeMenuAnchorEl(event.currentTarget);
- };
const handleTypeMenuClose = () => {
setTypeMenuAnchorEl(null);
@@ -198,27 +292,12 @@ const NetworkTableHeader = ({
const isSortingEnabledForType = guestTypeFilter === 'all';
- const handleColumnClick = (columnId) => {
+ const handleColumnClick = (columnId, forcedDirection) => {
// For normal columns, just requestSort
- requestSort(columnId);
- };
-
- // New handler for node column text click that sorts by node
- const handleNodeColumnTextClick = (e) => {
- e.stopPropagation(); // Prevent opening the dropdown
- requestSort('node');
- };
-
- // New handler for status column text click that sorts by status
- const handleStatusColumnTextClick = (e) => {
- e.stopPropagation(); // Prevent opening the dropdown
- requestSort('status');
- };
-
- // New handler for type column text click that sorts by type
- const handleTypeColumnTextClick = (e) => {
- e.stopPropagation(); // Prevent opening the dropdown
- requestSort('type');
+ console.log(`Requesting sort for column: ${columnId}`, forcedDirection ? `with forced direction: ${forcedDirection}` : '');
+
+ // Always pass the current columnId and the forcedDirection if provided
+ requestSort(columnId, forcedDirection);
};
const visibleColumnCount = Object.values(columnVisibility).filter(col => col.visible).length;
@@ -275,55 +354,81 @@ const NetworkTableHeader = ({
setDraggedColumn(columnId);
setPreviewOrder([...columnOrder]);
- const dragImage = document.createElement('div');
- dragImage.style.position = 'absolute';
- dragImage.style.width = '1px';
- dragImage.style.height = '1px';
- dragImage.style.top = '-1000px';
- document.body.appendChild(dragImage);
+ // Create a custom drag image
+ const draggedElement = e.currentTarget;
+ const rect = draggedElement.getBoundingClientRect();
- e.dataTransfer.setDragImage(dragImage, 0, 0);
+ const ghostElement = draggedElement.cloneNode(true);
+ ghostElement.style.width = `${rect.width}px`;
+ ghostElement.style.height = `${rect.height}px`;
+ ghostElement.style.backgroundColor = alpha(theme.palette.primary.main, 0.2);
+ ghostElement.style.boxShadow = `0 4px 8px ${alpha(theme.palette.primary.dark, 0.2)}`;
+ ghostElement.style.borderRadius = '4px';
+ ghostElement.style.opacity = '0.8';
+ ghostElement.style.position = 'absolute';
+ ghostElement.style.top = '-1000px';
+ ghostElement.style.left = '-1000px';
+ ghostElement.style.zIndex = '9999';
+ ghostElement.style.pointerEvents = 'none';
+
+ document.body.appendChild(ghostElement);
+ e.dataTransfer.setDragImage(ghostElement, rect.width / 2, rect.height / 2);
+ e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', columnId);
+ // Add a class to the body to show we're dragging
+ document.body.classList.add('column-dragging');
+
setTimeout(() => {
- document.body.removeChild(dragImage);
+ document.body.removeChild(ghostElement);
}, 0);
};
const handleDragOver = (e, columnId) => {
e.preventDefault();
- if (draggedColumn !== columnId) {
- setDragOverColumn(columnId);
-
- if (previewOrder && draggedColumn && columnId) {
- const newPreviewOrder = [...previewOrder];
- const sourceIndex = newPreviewOrder.indexOf(draggedColumn);
- const targetIndex = newPreviewOrder.indexOf(columnId);
-
- if (sourceIndex !== -1 && targetIndex !== -1) {
- newPreviewOrder.splice(sourceIndex, 1);
- newPreviewOrder.splice(targetIndex, 0, draggedColumn);
- console.log('Preview order updated:', newPreviewOrder);
- setPreviewOrder(newPreviewOrder);
- }
- }
+ if (draggedColumn === columnId) return;
+
+ setDragOverColumn(columnId);
+
+ // Add class to the element for highlight
+ const targetCell = e.currentTarget;
+ const dropPosition = getDropPosition(draggedColumn, columnId);
+
+ // Remove any existing highlight classes first
+ targetCell.classList.remove('drop-highlight-before', 'drop-highlight-after');
+
+ // Add the appropriate highlight class
+ if (dropPosition === 'before') {
+ targetCell.classList.add('drop-highlight-before');
} else {
- setDragOverColumn(null);
+ targetCell.classList.add('drop-highlight-after');
+ }
+
+ // Update the preview order if needed
+ if (columnOrder && draggedColumn && columnId !== draggedColumn) {
+ const fromIndex = columnOrder.indexOf(draggedColumn);
+ const toIndex = columnOrder.indexOf(columnId);
+
+ if (fromIndex !== -1 && toIndex !== -1) {
+ const newOrder = [...columnOrder];
+ newOrder.splice(fromIndex, 1);
+ newOrder.splice(toIndex, 0, draggedColumn);
+ setPreviewOrder(newOrder);
+ }
}
};
- const handleDrop = (e, targetColumnId) => {
+ const handleDrop = (e, columnId) => {
e.preventDefault();
- if (previewOrder && draggedColumn) {
- console.log('Setting column order from preview:', previewOrder);
- setColumnOrder([...previewOrder]);
- }
- else if (draggedColumn && targetColumnId && draggedColumn !== targetColumnId) {
+ // Remove highlight classes
+ e.currentTarget.classList.remove('drop-highlight-before', 'drop-highlight-after');
+
+ if (draggedColumn !== columnId && columnOrder) {
console.log('Using fallback drop logic');
const sourceIndex = columnOrder.indexOf(draggedColumn);
- const targetIndex = columnOrder.indexOf(targetColumnId);
+ const targetIndex = columnOrder.indexOf(columnId);
if (sourceIndex !== -1 && targetIndex !== -1) {
const newOrder = [...columnOrder];
@@ -339,16 +444,31 @@ const NetworkTableHeader = ({
};
const handleDragEnd = () => {
+ // Remove all highlight classes from the table cells
+ document.querySelectorAll('.drop-highlight-before, .drop-highlight-after')
+ .forEach(el => {
+ el.classList.remove('drop-highlight-before', 'drop-highlight-after');
+ });
+
setDraggedColumn(null);
setDragOverColumn(null);
setPreviewOrder(null);
+
+ // Remove the dragging class
+ document.body.classList.remove('column-dragging');
};
- const getDropPosition = (draggedId, targetId) => {
- if (!draggedId || !targetId || draggedId === targetId) return null;
- const draggedIndex = columnOrder.indexOf(draggedId);
+ const getDropPosition = (sourceId, targetId) => {
+ if (!columnOrder || !sourceId || !targetId) return 'before';
+
+ const sourceIndex = columnOrder.indexOf(sourceId);
const targetIndex = columnOrder.indexOf(targetId);
- return draggedIndex < targetIndex ? 'after' : 'before';
+
+ if (sourceIndex === -1 || targetIndex === -1) return 'before';
+
+ // If source comes before target in the original order, drop after
+ // If source comes after target in the original order, drop before
+ return sourceIndex < targetIndex ? 'after' : 'before';
};
const displayOrder = previewOrder || columnOrder;
@@ -356,6 +476,191 @@ const NetworkTableHeader = ({
const selectedNodeObject = availableNodes.find(node => node.id === selectedNode);
const selectedNodeName = selectedNodeObject ? selectedNodeObject.name : 'All Nodes';
+ const showNotification = (message, variant = 'info') => {
+ enqueueSnackbar(message, {
+ variant,
+ autoHideDuration: 3000,
+ anchorOrigin: {
+ vertical: 'top',
+ horizontal: 'center',
+ }
+ });
+ };
+
+ // State for column header hover
+ const [hoveredColumn, setHoveredColumn] = useState(null);
+
+ // Function to handle column header mouse enter
+ const handleColumnMouseEnter = (columnId) => () => {
+ setHoveredColumn(columnId);
+ };
+
+ // Function to handle column header mouse leave
+ const handleColumnMouseLeave = () => {
+ setHoveredColumn(null);
+ };
+
+ // Function to hide column
+ const hideColumn = (columnId, e) => {
+ if (e) e.stopPropagation();
+ toggleColumnVisibility(columnId);
+ setHoveredColumn(null);
+ };
+
+ // Handle opening resource-specific filter
+ const handleResourceFilterClick = (event, resourceType) => {
+ event.stopPropagation();
+
+ // Direct reference to the main filter button's click handler
+ handleFilterButtonClick(event);
+
+ // Update filter to match the resource type
+ setTimeout(() => {
+ // Focus on the specific resource slider
+ const sliderElement = document.getElementById(`${resourceType}-filter-slider`);
+ if (sliderElement) {
+ sliderElement.focus();
+ }
+ }, 100);
+ };
+
+ // Format percentage for display
+ const formatPercentage = (value) => {
+ return `${value}%`;
+ };
+
+ // Function to get filter value by column ID
+ const getFilterValue = (columnId) => {
+ switch(columnId) {
+ case 'cpu': return filters.cpu || 0;
+ case 'memory': return filters.memory || 0;
+ case 'disk': return filters.disk || 0;
+ case 'download': return filters.download || 0;
+ case 'upload': return filters.upload || 0;
+ default: return 0;
+ }
+ };
+
+ // Check if a resource has an active filter
+ const hasActiveFilter = (columnId) => {
+ return getFilterValue(columnId) > 0;
+ };
+
+ // Function to render the column content based on the column type
+ const renderColumnContent = (column) => {
+ // Check if this column is currently being hovered over
+ const isHovered = hoveredColumn === column.id;
+
+ // Check if this column is a resource column that can have thresholds
+ const isResourceColumn = ['cpu', 'memory', 'disk', 'download', 'upload'].includes(column.id);
+
+ // Check if this column has an active search filter
+ const hasSearchFilter = activeFilteredColumns[column.id];
+
+ // Common sort indicator component for all column types
+ const SortIndicator = () => (
+
+ {sortConfig?.key === column.id ? (
+ sortConfig.direction === 'asc' ? (
+
+ ) : (
+
+ )
+ ) : (
+ // Invisible placeholder to reserve space
+
+ )}
+
+ );
+
+ // Common column title styling
+ const titleTypographyProps = {
+ variant: "body2",
+ sx: {
+ fontWeight: sortConfig?.key === column.id || hasSearchFilter ? 600 : 400,
+ color: hasSearchFilter
+ ? theme.palette.primary.main
+ : sortConfig?.key === column.id
+ ? theme.palette.mode === 'dark'
+ ? '#ffffff'
+ : theme.palette.primary.main
+ : 'inherit'
+ }
+ };
+
+ // Common text click handler for ALL columns
+ const handleTextClick = (e) => {
+ e.stopPropagation();
+
+ // For resource columns, default to descending sort first time
+ if (isResourceColumn && (!sortConfig || sortConfig.key !== column.id)) {
+ console.log(`Resource column ${column.id} clicked - defaulting to DESC sort`);
+ requestSort(column.id, 'desc');
+ } else {
+ // First check if this is the currently sorted column
+ if (sortConfig && sortConfig.key === column.id) {
+ // If already sorted, explicitly toggle direction
+ const newDirection = sortConfig.direction === 'asc' ? 'desc' : 'asc';
+ console.log(`Column ${column.id} clicked - toggling from ${sortConfig.direction} to ${newDirection}`);
+ requestSort(column.id, newDirection);
+ } else {
+ // New column sort, use default toggle
+ console.log(`Column ${column.id} clicked - new sort`);
+ requestSort(column.id);
+ }
+ }
+ };
+
+ // Render the appropriate column content
+ return (
+
+
+ {/* Removed search filter icon */}
+
+
+ {column.label || column.id}
+
+
+
+ {/* Threshold indicator */}
+ {hasActiveFilter(column.id) && (
+
+ ({formatPercentage(getFilterValue(column.id))}+)
+
+ )}
+
+
+ );
+ };
+
return (
@@ -388,246 +693,101 @@ const NetworkTableHeader = ({
{visibleColumns.map(column => (
handleDragStart(e, column.id)}
+ onDragOver={(e) => handleDragOver(e, column.id)}
+ onDrop={(e) => handleDrop(e, column.id)}
+ onDragEnd={handleDragEnd}
onClick={(e) => {
- // For special columns, don't do anything on the cell click
- // since we're handling clicks on the components inside
- if (column.id !== 'node' && column.id !== 'status' && column.id !== 'type') {
- handleColumnClick(column.id);
+ // Only handle direct cell clicks - all columns now just sort, no dropdown functionality
+ if (e.target === e.currentTarget) {
+ const handleTextClick = (columnId) => {
+ // For resource columns, default to descending sort first time
+ const isResourceColumn = ['cpu', 'memory', 'disk', 'download', 'upload'].includes(columnId);
+
+ if (isResourceColumn && (!sortConfig || sortConfig.key !== columnId)) {
+ console.log(`Resource column ${columnId} clicked - defaulting to DESC sort`);
+ requestSort(columnId, 'desc');
+ } else {
+ // First check if this is the currently sorted column
+ if (sortConfig && sortConfig.key === columnId) {
+ // If already sorted, explicitly toggle direction
+ const newDirection = sortConfig.direction === 'asc' ? 'desc' : 'asc';
+ console.log(`Column ${columnId} clicked - toggling from ${sortConfig.direction} to ${newDirection}`);
+ requestSort(columnId, newDirection);
+ } else {
+ // New column sort, use default toggle
+ console.log(`Column ${columnId} clicked - new sort`);
+ requestSort(columnId);
+ }
+ }
+ };
+
+ // All columns handle sorting the same way
+ handleTextClick(column.id);
}
}}
+ onMouseEnter={handleColumnMouseEnter(column.id)}
+ onMouseLeave={handleColumnMouseLeave}
sx={{
- width: columnWidths[column.id] || 'auto',
- minWidth: getMinWidthForColumn(column.id),
- backgroundColor: theme.palette.background.paper,
+ width: getColumnWidth(column.id),
+ minWidth: `${getMinWidthForColumn(column.id)}px`,
+ maxWidth: autoSizeColumns.includes(column.id) ? '300px' : 'none',
+ padding: fixedNarrowColumns.includes(column.id) ? '0px 8px' : '16px 8px',
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ backgroundColor: draggedColumn === column.id
+ ? alpha(theme.palette.primary.main, 0.1)
+ : dragOverColumn === column.id
+ ? alpha(theme.palette.primary.main, 0.05)
+ : theme.palette.background.paper,
borderBottom: '1px solid',
borderBottomColor: 'divider',
borderTop: 'none',
- boxShadow: 'none',
- ...((column.id === 'status' || column.id === 'type') && {
+ boxShadow: draggedColumn === column.id
+ ? `0 0 8px ${alpha(theme.palette.primary.main, 0.5)}`
+ : 'none',
+ ...(fixedNarrowColumns.includes(column.id) && {
textAlign: 'center',
padding: '0px 8px'
}),
- cursor: 'pointer',
- '&:hover': {
- backgroundColor: theme.palette.mode === 'dark'
- ? alpha(theme.palette.primary.light, 0.15)
- : alpha(theme.palette.primary.light, 0.1)
+ cursor: 'grab',
+ userSelect: 'auto',
+ position: 'relative',
+ transition: 'background-color 0.2s, transform 0.1s, box-shadow 0.2s',
+
+ backgroundColor: hoveredColumn === column.id
+ ? alpha(theme.palette.primary.light, 0.1)
+ : draggedColumn === column.id
+ ? alpha(theme.palette.primary.main, 0.15)
+ : alpha(theme.palette.primary.light, 0.05),
+ '& .drag-handle': {
+ visibility: 'visible',
+ opacity: 0.7
+ },
+ '&:active': {
+ cursor: 'grabbing',
+ backgroundColor: alpha(theme.palette.primary.main, 0.15)
}
}}
>
- {column.id === 'node' ? (
-
-
- {column.label || column.id}
-
- handleNodeColumnClick(e)}
- sx={{
- fontSize: '1rem',
- opacity: activeFilteredColumns['node'] ? 1 : 0.7,
- ml: 0.5,
- color: activeFilteredColumns['node']
- ? theme.palette.primary.main
- : 'inherit',
- cursor: 'pointer',
- p: 0.3,
- borderRadius: '50%',
- backgroundColor: activeFilteredColumns['node'] ?
- alpha(theme.palette.primary.main, 0.1) : 'transparent',
- '&:hover': {
- backgroundColor: alpha(theme.palette.primary.main, 0.1)
- }
- }}
- />
-
- ) : column.id === 'status' ? (
-
-
- {column.label || column.id}
-
- handleStatusColumnClick(e)}
- sx={{
- fontSize: '1rem',
- opacity: activeFilteredColumns['status'] ? 1 : 0.7,
- ml: 0.5,
- color: activeFilteredColumns['status']
- ? theme.palette.primary.main
- : 'inherit',
- cursor: 'pointer',
- p: 0.3,
- borderRadius: '50%',
- backgroundColor: activeFilteredColumns['status'] ?
- alpha(theme.palette.primary.main, 0.1) : 'transparent',
- '&:hover': {
- backgroundColor: alpha(theme.palette.primary.main, 0.1)
- }
- }}
- />
-
- ) : column.id === 'type' ? (
-
-
- {column.label || column.id}
-
- handleTypeColumnClick(e)}
- sx={{
- fontSize: '1rem',
- opacity: activeFilteredColumns['type'] ? 1 : 0.7,
- ml: 0.5,
- color: activeFilteredColumns['type']
- ? theme.palette.primary.main
- : 'inherit',
- cursor: 'pointer',
- p: 0.3,
- borderRadius: '50%',
- backgroundColor: activeFilteredColumns['type'] ?
- alpha(theme.palette.primary.main, 0.1) : 'transparent',
- '&:hover': {
- backgroundColor: alpha(theme.palette.primary.main, 0.1)
- }
- }}
- />
-
- ) : column.id === 'name' || column.id === 'id' ? (
+
-
- {column.label || column.id}
- {sortConfig?.key === column.id && (
-
- {sortConfig.direction === 'asc' ? '▲' : '▼'}
-
- )}
-
+ {renderColumnContent(column)}
- ) : (
-
-
- {column.label || column.id}
- {sortConfig?.key === column.id && (
-
- {sortConfig.direction === 'asc' ? '▲' : '▼'}
-
- )}
-
-
- )}
+
))}
>
@@ -963,9 +1123,11 @@ const NetworkTableHeader = ({
sx: {
borderRadius: 2,
overflow: 'hidden',
- width: 300
+ width: 300,
+ maxHeight: '80vh'
}
}}
+ sx={{ zIndex: 1500 }}
>
@@ -1043,29 +1205,6 @@ const NetworkTableHeader = ({
justifyContent: 'space-between'
}}>
- {
- e.stopPropagation();
- handleDragStart(e, columnId);
- }}
- sx={{
- opacity: draggedColumn === columnId ? 1 : 0.3,
- mr: 1,
- cursor: draggedColumn === columnId ? 'grabbing' : 'grab',
- display: 'flex',
- alignItems: 'center',
- color: draggedColumn === columnId ? 'primary.main' : 'text.secondary',
- '&:hover': {
- opacity: 1,
- color: 'primary.main'
- }
- }}
- onClick={(e) => e.stopPropagation()}
- >
-
-
{
const minWidths = {
- node: 70,
- type: 40,
- id: 60,
- status: 40,
- name: 130,
- cpu: 100,
- memory: 100,
- disk: 100,
- download: 90,
- upload: 90,
- uptime: 85
+ // Fixed narrow columns - add a few pixels to accommodate sort icon
+ type: 50, // Very small - just "VM" or "CT"
+ id: 60, // Very small - just numeric IDs
+ status: 50, // Minimal - just an icon
+
+ // Auto-sized columns
+ name: 120, // Names - minimum width
+ node: 80, // Node names - minimum width (reduced from 100)
+
+ // Flexible equal columns
+ cpu: 100, // Medium - progress bar with percentage
+ memory: 140, // Progress bar with byte values
+ disk: 140, // Progress bar with byte values
+
+ // Fixed width columns
+ download: 90, // Network rates
+ upload: 90, // Network rates
+ uptime: 90 // Time display
};
- return minWidths[columnId] || 90;
+ return minWidths[columnId] || 80;
};
export default NetworkTableHeader;
\ No newline at end of file