import { FC, useEffect, useState } from 'react'; import { Box as MUIBox, BoxProps as MUIBoxProps, IconButton as MUIIconButton, IconButtonProps as MUIIconButtonProps, iconButtonClasses as muiIconButtonClasses, } from '@mui/material'; import { Add as MUIAddIcon, Check as MUICheckIcon, Close as MUICloseIcon, DragHandle as MUIDragHandleIcon, } from '@mui/icons-material'; import { DataGrid as MUIDataGrid, DataGridProps as MUIDataGridProps, gridClasses as muiGridClasses, } from '@mui/x-data-grid'; import API_BASE_URL from '../../lib/consts/API_BASE_URL'; import { BLUE, GREY, TEXT } from '../../lib/consts/DEFAULT_THEME'; import Decorator from '../../components/Decorator'; import { InnerPanel, InnerPanelHeader, Panel, PanelHeader, } from '../../components/Panels'; import periodicFetch from '../../lib/fetchers/periodicFetch'; import Spinner from '../../components/Spinner'; import sumstring from '../../lib/sumstring'; import { BodyText, BodyTextProps, HeaderText } from '../../components/Text'; import OutlinedInputWithLabel from '../../components/OutlinedInputWithLabel'; type NetworkInput = { interfaces: NetworkInterfaceOverviewMetadata[]; ipAddress: string; name: string; subnetMask: string; }; type NetworkInterfaceInputMap = Record< string, { isApplied?: boolean; } >; const MOCK_NICS: NetworkInterfaceOverviewMetadata[] = [ { networkInterfaceUUID: 'fe299134-c8fe-47bd-ab7a-3aa95eada1f6', networkInterfaceMACAddress: '52:54:00:d2:31:36', networkInterfaceName: 'ens10', networkInterfaceState: 'up', networkInterfaceSpeed: 10000, networkInterfaceOrder: 1, }, { networkInterfaceUUID: 'a652bfd5-61ac-4495-9881-185be8a2ac74', networkInterfaceMACAddress: '52:54:00:d4:4d:b5', networkInterfaceName: 'ens11', networkInterfaceState: 'up', networkInterfaceSpeed: 10000, networkInterfaceOrder: 2, }, { networkInterfaceUUID: 'b8089b40-0969-49c3-ad65-2470ddb420ef', networkInterfaceMACAddress: '52:54:00:ba:f5:a3', networkInterfaceName: 'ens3', networkInterfaceState: 'up', networkInterfaceSpeed: 10000, networkInterfaceOrder: 3, }, { networkInterfaceUUID: '42a17465-31b1-4e47-9a91-f803f22ffcc1', networkInterfaceMACAddress: '52:54:00:ae:31:70', networkInterfaceName: 'ens9', networkInterfaceState: 'up', networkInterfaceSpeed: 10000, networkInterfaceOrder: 4, }, ]; const DataGridCellText: FC = ({ ...dataGridCellTextRestProps }) => ( ); type BriefNetworkInterfaceOptionalProps = { onClose?: MUIIconButtonProps['onClick']; }; const BRIEF_NETWORK_INTERFACE_DEFAULT_PROPS: Required< Omit > & Pick = { onClose: undefined, }; const BriefNetworkInterface: FC< MUIBoxProps & BriefNetworkInterfaceOptionalProps & { networkInterface: NetworkInterfaceOverviewMetadata; } > = ({ networkInterface: { networkInterfaceName, networkInterfaceState }, onClose, sx: rootSx, ...restRootProps }) => ( :not(:first-child)': { marginLeft: '.5em' }, ...rootSx, }, ...restRootProps, }} > {onClose && ( )} ); BriefNetworkInterface.defaultProps = BRIEF_NETWORK_INTERFACE_DEFAULT_PROPS; const createNetworkInterfaceTableColumns = ( handleDragMouseDown: ( row: NetworkInterfaceOverviewMetadata, ...eventArgs: Parameters> ) => void, networkInterfaceInputMap: NetworkInterfaceInputMap, ): MUIDataGridProps['columns'] => [ { align: 'center', field: '', renderCell: ({ row }) => { const { isApplied } = networkInterfaceInputMap[row.networkInterfaceUUID] || false; let cursor = 'grab'; let handleMouseDown: MUIBoxProps['onMouseDown'] = (...eventArgs) => { handleDragMouseDown(row, ...eventArgs); }; let icon = ; if (isApplied) { cursor = 'auto'; handleMouseDown = undefined; icon = ; } return ( {icon} ); }, sortable: false, width: 1, }, { field: 'networkInterfaceName', flex: 1, headerName: 'Name', renderCell: ({ row: { networkInterfaceState } = {}, value }) => ( :not(:first-child)': { marginLeft: '.5em' }, }} > ), sortComparator: (v1, v2) => sumstring(v1) - sumstring(v2), }, { field: 'networkInterfaceMACAddress', flex: 1, headerName: 'MAC', renderCell: ({ value }) => , }, { field: 'networkInterfaceState', headerName: 'State', renderCell: ({ value }) => { const state = String(value); return ( ); }, }, { field: 'networkInterfaceSpeed', flex: 1, headerName: 'Speed', renderCell: ({ value }) => ( ), }, { field: 'networkInterfaceOrder', flex: 1, headerName: 'Order', }, ]; const NetworkInterfaceList: FC = () => { const [dragMousePosition, setDragMousePosition] = useState<{ x: number; y: number; }>({ x: 0, y: 0 }); const [networkInterfaceInputMap, setNetworkInterfaceInputMap] = useState({}); const [networkInputs] = useState([ { ipAddress: '10.200.1.1', name: 'Back-Channel Network 1', interfaces: [], subnetMask: '255.255.0.0', }, { ipAddress: '10.201.1.1', name: 'Internet-Facing Network 1', interfaces: [], subnetMask: '255.255.0.0', }, ]); const [networkInterfaceHeld, setNetworkInterfaceHeld] = useState< NetworkInterfaceOverviewMetadata | undefined >(); const { data: networkInterfaces = MOCK_NICS, isLoading } = periodicFetch< NetworkInterfaceOverviewMetadata[] >(`${API_BASE_URL}/network-interface`, { refreshInterval: 2000, onSuccess: (data) => { if (data instanceof Array) { const map = data.reduce( (reduceContainer, { networkInterfaceUUID }) => { reduceContainer[networkInterfaceUUID] = networkInterfaceInputMap[networkInterfaceUUID] || {}; return reduceContainer; }, {}, ); setNetworkInterfaceInputMap(map); } }, }); const clearNetworkInterfaceHeld = () => { setNetworkInterfaceHeld(undefined); }; let createDropMouseUpHandler: ( interfaces: NetworkInterfaceOverviewMetadata[], ) => MUIBoxProps['onMouseUp']; let floatingNetworkInterface: JSX.Element = <>; let handleCreateNetworkMouseUp: MUIBoxProps['onMouseUp']; let handlePanelMouseMove: MUIBoxProps['onMouseMove']; if (networkInterfaceHeld) { const { networkInterfaceUUID } = networkInterfaceHeld; createDropMouseUpHandler = (interfaces: NetworkInterfaceOverviewMetadata[]) => () => { interfaces.push(networkInterfaceHeld); networkInterfaceInputMap[networkInterfaceUUID].isApplied = true; }; floatingNetworkInterface = ( ); handleCreateNetworkMouseUp = () => { networkInputs.push({ ipAddress: '', name: '', interfaces: [networkInterfaceHeld], subnetMask: '', }); networkInterfaceInputMap[networkInterfaceUUID].isApplied = true; }; handlePanelMouseMove = ({ nativeEvent: { clientX, clientY } }) => { setDragMousePosition({ x: clientX, y: clientY, }); }; } useEffect(() => { const map = networkInterfaces.reduce( (reduceContainer, { networkInterfaceUUID }) => { reduceContainer[networkInterfaceUUID] = networkInterfaceInputMap[networkInterfaceUUID] || {}; return reduceContainer; }, {}, ); setNetworkInterfaceInputMap(map); // This block inits the input map for the MOCK_NICS. // TODO: remove after testing. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( {floatingNetworkInterface} {isLoading ? ( ) : ( :not(:first-child)': { marginTop: '1em', }, }} > { setDragMousePosition({ x: clientX, y: clientY, }); setNetworkInterfaceHeld(row); }, networkInterfaceInputMap, )} disableColumnMenu disableSelectionOnClick getRowId={({ networkInterfaceUUID }) => networkInterfaceUUID} hideFooter rows={networkInterfaces} sx={{ color: GREY, [`& .${muiIconButtonClasses.root}`]: { color: 'inherit', }, [`& .${muiGridClasses.cell}:focus`]: { outline: 'none', }, }} /> *': { marginBottom: '1em', marginTop: '1em', minWidth: '10em', width: '25%', }, '& > :not(:first-child)': { marginLeft: '1em', }, }} > {networkInputs.map( ({ interfaces, ipAddress, name, subnetMask }, networkIndex) => ( :not(:first-child)': { marginTop: '1em', }, }} > :not(:first-child)': { marginTop: '.3em', }, }} > {interfaces.length > 0 ? ( interfaces.map( (networkInterface, networkInterfaceIndex) => { const { networkInterfaceUUID } = networkInterface; return ( { interfaces.splice(networkInterfaceIndex, 1); networkInterfaceInputMap[ networkInterfaceUUID ].isApplied = false; if ( networkIndex > 1 && interfaces.length === 0 ) { networkInputs.splice(networkIndex, 1); } setNetworkInterfaceInputMap({ ...networkInterfaceInputMap, }); }} /> ); }, ) ) : ( )} ), )} )} ); }; const Init: FC = () => ( ); export default Init;