From 77e5392662b50d631efa6b650cfac80c0c5a652f Mon Sep 17 00:00:00 2001 From: thediveo Date: Tue, 13 Feb 2024 10:51:24 +0100 Subject: [PATCH 1/7] wip: new multi-nif capture icons: multi-select off/on, nif check Signed-off-by: thediveo --- webui/icons/Capture.svg | 50 +++++++++++++--------- webui/icons/CaptureCheck.svg | 69 ++++++++++++++++++++++++++++++ webui/icons/CaptureMulti.svg | 69 ++++++++++++++++++++++++++++++ webui/icons/CaptureMultiOn.svg | 69 ++++++++++++++++++++++++++++++ webui/src/icons/Capture.tsx | 2 +- webui/src/icons/CaptureCheck.tsx | 5 +++ webui/src/icons/CaptureMulti.tsx | 5 +++ webui/src/icons/CaptureMultiOn.tsx | 5 +++ 8 files changed, 252 insertions(+), 22 deletions(-) create mode 100644 webui/icons/CaptureCheck.svg create mode 100644 webui/icons/CaptureMulti.svg create mode 100644 webui/icons/CaptureMultiOn.svg create mode 100644 webui/src/icons/CaptureCheck.tsx create mode 100644 webui/src/icons/CaptureMulti.tsx create mode 100644 webui/src/icons/CaptureMultiOn.tsx diff --git a/webui/icons/Capture.svg b/webui/icons/Capture.svg index 86dfcfe..b891359 100644 --- a/webui/icons/Capture.svg +++ b/webui/icons/Capture.svg @@ -1,19 +1,19 @@ + sodipodi:docname="Capture.svg" + inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> @@ -22,7 +22,6 @@ image/svg+xml - @@ -37,25 +36,34 @@ guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" - inkscape:window-width="2160" - inkscape:window-height="1350" + inkscape:window-width="1920" + inkscape:window-height="1137" id="namedview8" showgrid="true" inkscape:pagecheckerboard="false" showguides="true" - inkscape:zoom="27.812867" - inkscape:cx="13.418015" - inkscape:cy="14.477141" - inkscape:window-x="-11" - inkscape:window-y="-11" + inkscape:zoom="32" + inkscape:cx="6.15625" + inkscape:cy="14.140625" + inkscape:window-x="-8" + inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg6" - inkscape:document-rotation="0"> + inkscape:document-rotation="0" + inkscape:showpageshadow="0" + inkscape:deskcolor="#d1d1d1"> + id="grid4526" + originx="0" + originy="0" + spacingy="1" + spacingx="1" + units="px" + visible="true" /> + style="color:#000000;fill:#000000;stroke-linejoin:round;-inkscape-stroke:none" + d="M 15.853516,5.0117187 C 14.286645,5.2438478 12.136948,6.0574516 10.298828,7.7988281 8.6361458,9.3740009 7.3410928,11.785581 7.0976562,15 H 3 v 2 H 8 A 1.0001,1.0001 0 0 0 9,16 C 9,12.752384 10.188904,10.65862 11.675781,9.25 12.671888,8.3063201 13.7601,7.7840415 14.757813,7.4277344 14.037957,10.774242 14.005339,13.091169 15.048828,16.308594 A 1.0001,1.0001 0 0 0 16,17 h 5 V 15 H 16.814453 C 15.905337,11.964915 15.876696,10.34569 16.966797,6.2578125 A 1.0001,1.0001 0 0 0 15.853516,5.0117187 Z" + id="path1" /> diff --git a/webui/icons/CaptureCheck.svg b/webui/icons/CaptureCheck.svg new file mode 100644 index 0000000..3a8506b --- /dev/null +++ b/webui/icons/CaptureCheck.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/webui/icons/CaptureMulti.svg b/webui/icons/CaptureMulti.svg new file mode 100644 index 0000000..2bf7f4c --- /dev/null +++ b/webui/icons/CaptureMulti.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/webui/icons/CaptureMultiOn.svg b/webui/icons/CaptureMultiOn.svg new file mode 100644 index 0000000..a321812 --- /dev/null +++ b/webui/icons/CaptureMultiOn.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/webui/src/icons/Capture.tsx b/webui/src/icons/Capture.tsx index 0f27163..30a101c 100644 --- a/webui/src/icons/Capture.tsx +++ b/webui/src/icons/Capture.tsx @@ -1,5 +1,5 @@ // autogenerated from icon svg file "icons/Capture.svg", do not edit; import * as React from 'react'; import { SvgIcon, SvgIconProps } from '@mui/material'; -export const CaptureIcon = (props: SvgIconProps) => ; +export const CaptureIcon = (props: SvgIconProps) => ; export default CaptureIcon; \ No newline at end of file diff --git a/webui/src/icons/CaptureCheck.tsx b/webui/src/icons/CaptureCheck.tsx new file mode 100644 index 0000000..88ecbbc --- /dev/null +++ b/webui/src/icons/CaptureCheck.tsx @@ -0,0 +1,5 @@ +// autogenerated from icon svg file "icons/CaptureCheck.svg", do not edit; +import * as React from 'react'; +import { SvgIcon, SvgIconProps } from '@mui/material'; +export const CaptureCheckIcon = (props: SvgIconProps) => ; +export default CaptureCheckIcon; \ No newline at end of file diff --git a/webui/src/icons/CaptureMulti.tsx b/webui/src/icons/CaptureMulti.tsx new file mode 100644 index 0000000..89ada5d --- /dev/null +++ b/webui/src/icons/CaptureMulti.tsx @@ -0,0 +1,5 @@ +// autogenerated from icon svg file "icons/CaptureMulti.svg", do not edit; +import * as React from 'react'; +import { SvgIcon, SvgIconProps } from '@mui/material'; +export const CaptureMultiIcon = (props: SvgIconProps) => ; +export default CaptureMultiIcon; \ No newline at end of file diff --git a/webui/src/icons/CaptureMultiOn.tsx b/webui/src/icons/CaptureMultiOn.tsx new file mode 100644 index 0000000..225d8b5 --- /dev/null +++ b/webui/src/icons/CaptureMultiOn.tsx @@ -0,0 +1,5 @@ +// autogenerated from icon svg file "icons/CaptureMultiOn.svg", do not edit; +import * as React from 'react'; +import { SvgIcon, SvgIconProps } from '@mui/material'; +export const CaptureMultiOnIcon = (props: SvgIconProps) => ; +export default CaptureMultiOnIcon; \ No newline at end of file From e885f24d4ce89b834dd9882390bb93b613c0919c Mon Sep 17 00:00:00 2001 From: thediveo Date: Tue, 13 Feb 2024 11:35:50 +0100 Subject: [PATCH 2/7] wip: multi-nif mode button handling, hide capture nif+containee buttons Signed-off-by: thediveo --- .../netnsplaincard/NetnsPlainCard.tsx | 95 ++++++++++++------- 1 file changed, 60 insertions(+), 35 deletions(-) diff --git a/webui/src/components/netnsplaincard/NetnsPlainCard.tsx b/webui/src/components/netnsplaincard/NetnsPlainCard.tsx index f192e9b..d03139d 100644 --- a/webui/src/components/netnsplaincard/NetnsPlainCard.tsx +++ b/webui/src/components/netnsplaincard/NetnsPlainCard.tsx @@ -2,9 +2,9 @@ // // SPDX-License-Identifier: MIT -import React from 'react' +import React, { ChangeEvent, useState } from 'react' -import { IconButton, styled, Tooltip } from '@mui/material' +import { Box, Checkbox, Fade, IconButton, styled, Tooltip } from '@mui/material' import { Paper } from '@mui/material' import FullscreenIcon from '@mui/icons-material/Fullscreen' @@ -14,6 +14,9 @@ import { useContextualId } from 'components/idcontext' import { NifBadge } from 'components/nifbadge' import { NifNavigator } from 'components/nifnavigator' import { RoutingEtcIcon } from 'icons/RoutingEtc' +import CaptureMultiIcon from 'icons/CaptureMulti' +import CaptureMultiOnIcon from 'icons/CaptureMultiOn' +import CaptureIcon from 'icons/Capture' const NetnsPaper = styled(Paper)(({ theme }) => ({ @@ -176,7 +179,7 @@ const StretchedNif = styled(NifNavigator)(() => ({ width: '100%', })) -const MaximizeButton = styled(IconButton)(({ theme }) => ({ +const CardButtonBox = styled(Box)(({ theme }) => ({ float: 'right', // Move the right-floated button partly back into the padding, as the // button has a large "corona" and we thus get a more pleasing visual @@ -184,7 +187,7 @@ const MaximizeButton = styled(IconButton)(({ theme }) => ({ // the top of the network namespace card. position: 'relative', top: theme.spacing(-1), - left: theme.spacing(1), + right: theme.spacing(-1), })) export interface NetnsPlainCardProps { @@ -239,8 +242,11 @@ export interface NetnsPlainCardProps { * up to its parent. Use the callbacks, Luke! */ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZoom, families, className }: NetnsPlainCardProps) => { + const netnsid = useContextualId(netnsId(netns)) + const [selectNifs, setSelectNifs] = useState(false) + const nifsWithoutBridgePorts = Object.values(netns.nifs) .filter(nif => !nif.master || nif.master.kind !== 'bridge') .sort(orderNifByName) @@ -264,7 +270,7 @@ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZo > @@ -307,7 +313,7 @@ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZo ) => { + setSelectNifs(e.target.checked) + } + return ( - - {/* only render the zoom button when there's a callback for it ;) */} - {onNetnsZoom && - - - - - } - - {/* render the containees of this network namespace. */} - - {containeesOfNetns(netns) - .sort(sortContaineesByName) - .map(cntr => - } - onClick={onContaineeZoom as (_: Containee) => void} - />)} - - - {/* and finally render all the network interfaces in this namespace */} - {renderNifs()} - + + + + + + + + } + checkedIcon={} + onChange={handleMultiNic} + /> + {/* only render the zoom button when there's a callback for it ;) */} + {onNetnsZoom && + + + + + } + + + {/* render the containees of this network namespace. */} + + {containeesOfNetns(netns) + .sort(sortContaineesByName) + .map(cntr => + } + onClick={onContaineeZoom as (_: Containee) => void} + />)} + + + {/* and finally render all the network interfaces in this namespace */} + {renderNifs()} + + ); } From fede92cc6ea687d12a94ecc25133306c36c36bb5 Mon Sep 17 00:00:00 2001 From: thediveo Date: Tue, 13 Feb 2024 13:57:35 +0100 Subject: [PATCH 3/7] wip: netns card w/ individual nif capture selection Signed-off-by: thediveo --- .../containeebadge/ContaineeBadge.tsx | 8 +++- .../netnsplaincard/NetnsPlainCard.tsx | 34 ++++++++++++++- webui/src/components/nifbadge/NifBadge.tsx | 42 ++++++++++++++++--- .../components/nifcheckbox/NifCheckbox.tsx | 41 ++++++++++++++++++ webui/src/components/nifcheckbox/index.tsx | 1 + .../components/nifnavigator/NifNavigator.tsx | 14 +++++++ .../targetcapture/TargetCapture.tsx | 2 +- 7 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 webui/src/components/nifcheckbox/NifCheckbox.tsx create mode 100644 webui/src/components/nifcheckbox/index.tsx diff --git a/webui/src/components/containeebadge/ContaineeBadge.tsx b/webui/src/components/containeebadge/ContaineeBadge.tsx index 262855f..04315eb 100644 --- a/webui/src/components/containeebadge/ContaineeBadge.tsx +++ b/webui/src/components/containeebadge/ContaineeBadge.tsx @@ -5,7 +5,7 @@ import React from 'react' import clsx from 'clsx' -import { Button, styled, Tooltip } from '@mui/material' +import { Box, Button, styled, Tooltip } from '@mui/material' import { containeeDescription, containeeState, ContainerState, containerStateString, Containee, containeeFullName, isPrivilegedContainer, isElevatedContainer, isContainer, GHOSTWIRE_LABEL_ROOT } from 'models/gw' import { ContaineeIcon } from 'utils/containeeicon' @@ -161,6 +161,7 @@ export interface ContaineeBadgeProps { notooltip?: boolean /** show an additional capture button? */ capture?: boolean + hideCapture?: boolean } /** @@ -203,6 +204,7 @@ export const ContaineeBadge = ({ endIcon, notooltip, capture, + hideCapture, onClick }: ContaineeBadgeProps) => { const privileged = isPrivilegedContainer(containee) @@ -220,7 +222,9 @@ export const ContaineeBadge = ({ const CeeIcon = ContaineeIcon(containee) - const shark = capture && + const shark = capture && (!hideCapture + ? + : ) const kingOfBoxIcon = privileged ? diff --git a/webui/src/components/netnsplaincard/NetnsPlainCard.tsx b/webui/src/components/netnsplaincard/NetnsPlainCard.tsx index d03139d..6ad21b9 100644 --- a/webui/src/components/netnsplaincard/NetnsPlainCard.tsx +++ b/webui/src/components/netnsplaincard/NetnsPlainCard.tsx @@ -246,6 +246,7 @@ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZo const netnsid = useContextualId(netnsId(netns)) const [selectNifs, setSelectNifs] = useState(false) + const [selectedNifs, setSelectedNifs] = useState([]) const nifsWithoutBridgePorts = Object.values(netns.nifs) .filter(nif => !nif.master || nif.master.kind !== 'bridge') @@ -255,6 +256,25 @@ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZo onNetnsZoom && onNetnsZoom(netns, 'routes') } + // keep an up-to-date list of selected network interfaces in this network + // namespace. + const handleNifChange = (event: React.ChangeEvent, nif: NetworkInterface) => { + const idx = selectedNifs.indexOf(nif.name) + if (event.target.checked) { + if (idx >= 0) { + return + } + setSelectedNifs([...selectedNifs, nif.name]) + return + } + if (idx < 0) { + return + } + const dup = [...selectedNifs] + dup.splice(idx, 1) + setSelectedNifs(dup) + } + // Render the individual network interfaces, with bridge network // interfaces "bridging" their port ("enslaved") network interfaces. const renderNifs = () => { @@ -271,10 +291,13 @@ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZo @@ -288,8 +311,11 @@ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZo ] // If the bridge has ports, then render them and place them into the @@ -314,11 +340,14 @@ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZo key={nif.name} nif={nif} capture={!selectNifs} + nifCheck={selectNifs} + checked={selectedNifs.includes(nif.name)} anchor families={families} stretch alignRight onNavigation={onNavigation} + onChange={handleNifChange} /> )} @@ -351,7 +380,7 @@ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZo } const handleMultiNic = (e: ChangeEvent) => { - setSelectNifs(e.target.checked) + setSelectNifs(!!e.target.checked) } return ( @@ -389,7 +418,8 @@ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZo key={containeeKey(cntr)} angled button - capture={!selectNifs} + capture + hideCapture={selectNifs} endIcon={} onClick={onContaineeZoom as (_: Containee) => void} />)} diff --git a/webui/src/components/nifbadge/NifBadge.tsx b/webui/src/components/nifbadge/NifBadge.tsx index c86a34f..320a4ab 100644 --- a/webui/src/components/nifbadge/NifBadge.tsx +++ b/webui/src/components/nifbadge/NifBadge.tsx @@ -18,6 +18,7 @@ import { TooltipWrapper } from 'utils/tooltipwrapper' import { relationClassName } from 'utils/relclassname' import { rgba } from 'utils/rgba' import { TargetCapture } from 'components/targetcapture' +import { NifCheckbox } from 'components/nifcheckbox' // The outer span holding together an optional "hardware" NIC icon as well @@ -72,6 +73,14 @@ const Capture = styled(TargetCapture)(() => ({ } })) +const FinCheckbox = styled(NifCheckbox)(() => ({ + marginLeft: '0.2em', + '&.alignright': { + marginLeft: 0, + marginRight: '0.2em', + } +})) + const Nif = styled(Button)(({ theme }) => ({ // General+basis badge styling... display: 'inline-block', @@ -235,6 +244,9 @@ export interface NifBadgeProps { notooltip?: boolean /** optionally show a capture button? */ capture?: boolean + /** optionally show a nif capture checkbox */ + nifCheck?: boolean + checked?: boolean /** * the IP address family/families to show (filter *through*, as opposed to * filtering *out*) as part of the tooltip. If left undefined, then it @@ -254,10 +266,15 @@ export interface NifBadgeProps { */ endIcon?: React.ReactNode /** - * optional callback handler: when set, the callback will be fired when - * the user clicks on the badge. + * optional callback handler: when set, the callback will fire when the user + * clicks on the badge. */ onClick?: (event: React.MouseEvent, nif: NetworkInterface) => void + /** + * optional callback handler: when set, the callback will fire when the + * network interface case checked/unchecked. + */ + onChange?: (event: React.ChangeEvent, nif: NetworkInterface) => void } /** @@ -307,11 +324,14 @@ export const NifBadge = ({ style, notooltip, capture, + nifCheck, + checked, families: fam, stretch, alignRight, endIcon, - onClick + onClick, + onChange }: NifBadgeProps) => { // We might later need the contextual base DOM element ID for constructing // the DOM element identifiers of related network interfaces in order to @@ -408,6 +428,12 @@ export const NifBadge = ({ } } + const HandleNifChange = (event: React.ChangeEvent) => { + if (onChange) { + onChange(event, nif) + } + } + const HWIcon = nifSRIOVIcons[nif.sriovrole || SRIOVRole.None] // With lots of information prepared we can finally render the badge, @@ -419,7 +445,10 @@ export const NifBadge = ({ style={style} id={anchor || !button ? nifDomId : ''} > - {capture && alignRight && } + {(capture || nifCheck) && alignRight && + (nifCheck + ? + : )} {nif.isPhysical && @@ -442,7 +471,10 @@ export const NifBadge = ({ className={clsx(nif.operstate.toLowerCase(), stretchBadgeClass, alignRightClass)} >{content}{endIcon} } - {capture && !alignRight && } + {(capture || nifCheck) && !alignRight && + (nifCheck + ? + : )} , tooltip, !notooltip) } diff --git a/webui/src/components/nifcheckbox/NifCheckbox.tsx b/webui/src/components/nifcheckbox/NifCheckbox.tsx new file mode 100644 index 0000000..a7b3a69 --- /dev/null +++ b/webui/src/components/nifcheckbox/NifCheckbox.tsx @@ -0,0 +1,41 @@ +// (c) Siemens AG 2024 +// +// SPDX-License-Identifier: MIT + +import { Checkbox, styled } from '@mui/material' +import CaptureCheckIcon from 'icons/CaptureCheck' +import { NetworkInterface, isOperational } from 'models/gw' +import React from 'react' + +const NifChecker = styled(Checkbox)(() => ({ + borderRadius: '50px', + maxWidth: '26px', + padding: '3px 3px', +})) + + +export interface NifCheckboxProps { + className?: string + nif: NetworkInterface + checked?: boolean + onChange: (event: React.ChangeEvent, checked: boolean) => void +} + +/** + * `NifCheckbox` renders a checkbox for selecting individual network interfaces + * for capture and displaying a shark fin instead of a check mark when selected. + * If the specified network interface isn't operational, then the checkbox + * cannot be selected. + */ +export const NifCheckbox = ({className, nif, checked, onChange}:NifCheckboxProps) => { + return } + disabled={!isOperational(nif)} + checked={checked} + onChange={onChange} + /> +} + +export default NifCheckbox diff --git a/webui/src/components/nifcheckbox/index.tsx b/webui/src/components/nifcheckbox/index.tsx new file mode 100644 index 0000000..b01f0cf --- /dev/null +++ b/webui/src/components/nifcheckbox/index.tsx @@ -0,0 +1 @@ +export * from './NifCheckbox' \ No newline at end of file diff --git a/webui/src/components/nifnavigator/NifNavigator.tsx b/webui/src/components/nifnavigator/NifNavigator.tsx index 6fad7a5..cc29415 100644 --- a/webui/src/components/nifnavigator/NifNavigator.tsx +++ b/webui/src/components/nifnavigator/NifNavigator.tsx @@ -53,6 +53,9 @@ export interface NifNavigatorProps { nif: NetworkInterface /** optionally show a network interface capture button? */ capture?: boolean + /** optionally show a nif capture checkbox */ + nifCheck?: boolean + checked?: boolean /** put an ID to this badge in any case (for placing the wiring). */ anchor?: boolean /** @@ -67,6 +70,11 @@ export interface NifNavigatorProps { * related network interface. */ onNavigation?: (nif: NetworkInterface) => void + /** + * optional callback handler: when set, the callback will fire when the + * network interface case checked/unchecked. + */ + onChange?: (event: React.ChangeEvent, nif: NetworkInterface) => void /** * the IP address family/families to show (filter *through*, as opposed to * filtering *out*). If left undefined, then it defaults to showing both @@ -97,10 +105,13 @@ export interface NifNavigatorProps { export const NifNavigator = ({ nif, capture, + nifCheck, anchor, stretch, alignRight, onNavigation, + onChange, + checked, families, className, style @@ -154,12 +165,15 @@ export const NifNavigator = ({ 0} stretch={stretch} alignRight={alignRight} onClick={handleBadgeClick} + onChange={onChange} className={className} style={style} /> diff --git a/webui/src/components/targetcapture/TargetCapture.tsx b/webui/src/components/targetcapture/TargetCapture.tsx index d3500d1..8543c03 100644 --- a/webui/src/components/targetcapture/TargetCapture.tsx +++ b/webui/src/components/targetcapture/TargetCapture.tsx @@ -17,7 +17,7 @@ import { keyframes } from '@mui/system'; // During development The (Capturing) Monolith can be enabled using // REACT_APP_ENABLE_MONOLITH. -const forceMonolith = !!import.meta.env.REACT_APP_ENABLE_MONOLITH +const forceMonolith = true //FIXME: !!import.meta.env.REACT_APP_ENABLE_MONOLITH // Calculate the static part of any remote live packet capture URL; it bases on // the base URI of the application, so we only calculate it once when this From 30f35480420fded18506ffafcad03a735ea7f471 Mon Sep 17 00:00:00 2001 From: thediveo Date: Tue, 13 Feb 2024 14:22:44 +0100 Subject: [PATCH 4/7] feat: nif selection capture in plain netns cards Signed-off-by: thediveo --- .../netnsplaincard/NetnsPlainCard.tsx | 34 +++++++++++++------ .../targetcapture/TargetCapture.tsx | 19 +++++++---- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/webui/src/components/netnsplaincard/NetnsPlainCard.tsx b/webui/src/components/netnsplaincard/NetnsPlainCard.tsx index 6ad21b9..0bbea82 100644 --- a/webui/src/components/netnsplaincard/NetnsPlainCard.tsx +++ b/webui/src/components/netnsplaincard/NetnsPlainCard.tsx @@ -16,7 +16,7 @@ import { NifNavigator } from 'components/nifnavigator' import { RoutingEtcIcon } from 'icons/RoutingEtc' import CaptureMultiIcon from 'icons/CaptureMulti' import CaptureMultiOnIcon from 'icons/CaptureMultiOn' -import CaptureIcon from 'icons/Capture' +import { TargetCapture } from 'components/targetcapture' const NetnsPaper = styled(Paper)(({ theme }) => ({ @@ -388,17 +388,29 @@ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZo - - - + + + + + - } - checkedIcon={} - onChange={handleMultiNic} - /> + + } + checkedIcon={} + onChange={handleMultiNic} + /> + {/* only render the zoom button when there's a callback for it ;) */} {onNetnsZoom && diff --git a/webui/src/components/targetcapture/TargetCapture.tsx b/webui/src/components/targetcapture/TargetCapture.tsx index 8543c03..d21f090 100644 --- a/webui/src/components/targetcapture/TargetCapture.tsx +++ b/webui/src/components/targetcapture/TargetCapture.tsx @@ -115,12 +115,16 @@ const SharkButton = styled(Button)(({ theme }) => ({ export interface TargetCaptureProps { /** capture target */ target: Containee | NetworkInterface | NetworkNamespace + /** optionally list of network interfaces to restrict capture to in target */ + targetNifs?: string[] /** button size */ size?: 'small' | 'medium' /** CSS class name(s) */ className?: string /** for use in help, always shows up, doesn't start capture. */ demo?: boolean + + disabled?: boolean } /** @@ -159,7 +163,7 @@ export interface TargetCaptureProps { * consistency checks and re-lookup will work the same as when specifying a * containee directly. */ -export const TargetCapture = ({ target, size = 'medium', className, demo }: TargetCaptureProps) => { +export const TargetCapture = ({ target, targetNifs, size = 'medium', className, demo, disabled }: TargetCaptureProps) => { // Is capturing enabled in the UI? const { enableMonolith } = useDynVars() @@ -180,16 +184,17 @@ export const TargetCapture = ({ target, size = 'medium', className, demo }: Targ // We never hide the capture button, but we'll disable it for a network // interface target which isn't operational. - const biting = !isNetworkInterface(target) || isOperational(target) + const biting = (!isNetworkInterface(target) || isOperational(target)) && !disabled // Unless a specific network interface is targetted, capture from all // "working" network interfaces of the network namespace involved. const nifs: string[] = - (isNetworkInterface(target) && [target.name]) - || (!!netns && Object.values(netns.nifs) - .filter(nif => isOperational(nif)) - .map(nif => nif.name)) - || [] + ((isNetworkInterface(target) && [target.name]) + || (!!netns && Object.values(netns.nifs) + .filter(nif => isOperational(nif)) + .map(nif => nif.name)) + || []) + .filter(nif => !targetNifs || targetNifs.includes(nif)) // Try to get a suitable containee that we can use to later add some useful // meta information to the packet capture stream. From db7a8a11bb35a8963e52075b3d82b9cfc7977b09 Mon Sep 17 00:00:00 2001 From: thediveo Date: Tue, 13 Feb 2024 14:54:56 +0100 Subject: [PATCH 5/7] fix: conditional rendering of capture interface selection only when capturing enabled or forced Signed-off-by: thediveo --- webui/.env.development | 1 + webui/package.json | 4 +- .../netnsplaincard/NetnsPlainCard.tsx | 57 +++++---- .../targetcapture/TargetCapture.tsx | 4 +- webui/src/index.tsx | 2 +- webui/vite.config.ts | 7 +- webui/wireshark-fin.svg | 109 ++++++++++++++++++ 7 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 webui/.env.development create mode 100644 webui/wireshark-fin.svg diff --git a/webui/.env.development b/webui/.env.development new file mode 100644 index 0000000..547642e --- /dev/null +++ b/webui/.env.development @@ -0,0 +1 @@ +#VITE_APP_ENABLE_MONOLITH=true diff --git a/webui/package.json b/webui/package.json index 5abd05c..4ce650e 100644 --- a/webui/package.json +++ b/webui/package.json @@ -70,8 +70,8 @@ "vite-tsconfig-paths": "^4.2.3" }, "scripts": { - "start": "REACT_APP_GIT_VERSION=${GIT_VERSION:-$(git describe)} REACT_APP_ENABLE_MONOLITH=true vite --port 3300", - "unstrict": "REACT_APP_GIT_VERSION=${GIT_VERSION:-$(git describe)} REACT_APP_UNSTRICT=true REACT_APP_ENABLE_MONOLITH=true vite --port 3300", + "start": "REACT_APP_GIT_VERSION=${GIT_VERSION:-$(git describe)} vite --port 3300", + "unstrict": "REACT_APP_GIT_VERSION=${GIT_VERSION:-$(git describe)} VITE_VITE_APP_UNSTRICT=true VITE_APP_ENABLE_MONOLITH=true vite --port 3300", "build": "REACT_APP_GIT_VERSION=${GIT_VERSION:-$(git describe)} vite build", "imagebuild": "REACT_APP_GIT_VERSION=${GIT_VERSION:-$(git describe)} vite build", "icons": "node genicons", diff --git a/webui/src/components/netnsplaincard/NetnsPlainCard.tsx b/webui/src/components/netnsplaincard/NetnsPlainCard.tsx index 0bbea82..2fb076d 100644 --- a/webui/src/components/netnsplaincard/NetnsPlainCard.tsx +++ b/webui/src/components/netnsplaincard/NetnsPlainCard.tsx @@ -17,6 +17,12 @@ import { RoutingEtcIcon } from 'icons/RoutingEtc' import CaptureMultiIcon from 'icons/CaptureMulti' import CaptureMultiOnIcon from 'icons/CaptureMultiOn' import { TargetCapture } from 'components/targetcapture' +import { useDynVars } from 'components/dynvars' + + +// During development The (Capturing) Monolith can be enabled using +// VITE_APP_ENABLE_MONOLITH. +const forceMonolith = !!import.meta.env.VITE_APP_ENABLE_MONOLITH const NetnsPaper = styled(Paper)(({ theme }) => ({ @@ -245,6 +251,9 @@ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZo const netnsid = useContextualId(netnsId(netns)) + // Is capturing enabled in the UI? + const { enableMonolith } = useDynVars() + const [selectNifs, setSelectNifs] = useState(false) const [selectedNifs, setSelectedNifs] = useState([]) @@ -387,30 +396,32 @@ export const NetnsPlainCard = ({ netns, onNavigation, onNetnsZoom, onContaineeZo - - - - - + + + + + + + + + + } + checkedIcon={} + onChange={handleMultiNic} + /> - - - } - checkedIcon={} - onChange={handleMultiNic} - /> - + {/* only render the zoom button when there's a callback for it ;) */} {onNetnsZoom && diff --git a/webui/src/components/targetcapture/TargetCapture.tsx b/webui/src/components/targetcapture/TargetCapture.tsx index d21f090..c69372a 100644 --- a/webui/src/components/targetcapture/TargetCapture.tsx +++ b/webui/src/components/targetcapture/TargetCapture.tsx @@ -16,8 +16,8 @@ import { keyframes } from '@mui/system'; // During development The (Capturing) Monolith can be enabled using -// REACT_APP_ENABLE_MONOLITH. -const forceMonolith = true //FIXME: !!import.meta.env.REACT_APP_ENABLE_MONOLITH +// VITE_APP_ENABLE_MONOLITH. +const forceMonolith = !!import.meta.env.VITE_APP_ENABLE_MONOLITH // Calculate the static part of any remote live packet capture URL; it bases on // the base URI of the application, so we only calculate it once when this diff --git a/webui/src/index.tsx b/webui/src/index.tsx index 08b5214..005dbb4 100644 --- a/webui/src/index.tsx +++ b/webui/src/index.tsx @@ -18,7 +18,7 @@ import '@fontsource/roboto-mono/400.css' // performance without strict-mode double rendering. const container = document.getElementById('root'); createRoot(container!).render( - import.meta.env.REACT_APP_UNSTRICT + import.meta.env.VITE_APP_UNSTRICT ? : ); \ No newline at end of file diff --git a/webui/vite.config.ts b/webui/vite.config.ts index eb47109..83b0b05 100644 --- a/webui/vite.config.ts +++ b/webui/vite.config.ts @@ -30,9 +30,10 @@ export default defineConfig(() => { base: './', server: { proxy: { - '/json': 'http://localhost:5000', - '/mobyshark': 'http://localhost:5000', - '/mobydig': 'http://localhost:5000', + '/json': 'http://localhost:5001', + '/mobyshark': 'http://localhost:5001', + '/discover/mobyshark': 'http://localhost:5001', + '/mobydig': 'http://localhost:5001', }, }, build: { diff --git a/webui/wireshark-fin.svg b/webui/wireshark-fin.svg new file mode 100644 index 0000000..b3af55e --- /dev/null +++ b/webui/wireshark-fin.svg @@ -0,0 +1,109 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + From 798b5c45efa597348ac3086da8a72e2caf81b766 Mon Sep 17 00:00:00 2001 From: thediveo Date: Tue, 13 Feb 2024 15:05:21 +0100 Subject: [PATCH 6/7] chore: tweak multi-fin icons Signed-off-by: thediveo --- webui/.env.development | 2 +- webui/icons/CaptureMulti.svg | 20 ++++++++++---------- webui/icons/CaptureMultiOn.svg | 8 ++++---- webui/src/icons/CaptureMulti.tsx | 2 +- webui/src/icons/CaptureMultiOn.tsx | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/webui/.env.development b/webui/.env.development index 547642e..5d7b991 100644 --- a/webui/.env.development +++ b/webui/.env.development @@ -1 +1 @@ -#VITE_APP_ENABLE_MONOLITH=true +VITE_APP_ENABLE_MONOLITH=true diff --git a/webui/icons/CaptureMulti.svg b/webui/icons/CaptureMulti.svg index 2bf7f4c..abbd65b 100644 --- a/webui/icons/CaptureMulti.svg +++ b/webui/icons/CaptureMulti.svg @@ -36,18 +36,18 @@ guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" - inkscape:window-width="1920" - inkscape:window-height="1137" + inkscape:window-width="1346" + inkscape:window-height="1160" id="namedview8" showgrid="true" inkscape:pagecheckerboard="false" showguides="true" - inkscape:zoom="45.254834" - inkscape:cx="12.28598" - inkscape:cy="13.755437" - inkscape:window-x="-8" - inkscape:window-y="-8" - inkscape:window-maximized="1" + inkscape:zoom="22.627417" + inkscape:cx="13.744388" + inkscape:cy="13.12567" + inkscape:window-x="2058" + inkscape:window-y="26" + inkscape:window-maximized="0" inkscape:current-layer="svg6" inkscape:document-rotation="0" inkscape:showpageshadow="0" @@ -63,7 +63,7 @@ visible="true" /> + d="M 12.005859 3 A 1.0001 1.0001 0 0 0 11.853516 3.0117188 C 10.286646 3.2438475 8.136946 4.0574533 6.2988281 5.7988281 C 4.4607104 7.540203 3 10.247626 3 14 A 1 1 0 0 0 4 15 A 1 1 0 0 0 5 14 C 5 10.752387 6.1889057 8.658619 7.6757812 7.25 C 8.6718869 6.3063209 9.7601009 5.7840411 10.757812 5.4277344 C 10.037957 8.7742392 10.00534 11.091172 11.048828 14.308594 A 1 1 0 0 0 12.03125 14.996094 C 11.396474 16.391135 11 18.053728 11 20 A 1 1 0 0 0 12 21 A 1 1 0 0 0 13 20 C 13 16.752387 14.188906 14.658619 15.675781 13.25 C 16.671887 12.306321 17.760101 11.784042 18.757812 11.427734 C 18.037958 14.77424 18.00534 17.091172 19.048828 20.308594 A 1 1 0 0 0 20.308594 20.951172 A 1 1 0 0 0 20.951172 19.691406 C 19.780536 16.081947 19.7839 14.693655 20.966797 10.257812 A 1.0001 1.0001 0 0 0 19.853516 9.0117188 C 18.286646 9.2438475 16.136946 10.057454 14.298828 11.798828 C 13.789668 12.28119 13.311031 12.84037 12.882812 13.472656 C 11.784853 10.027645 11.808683 8.6007383 12.966797 4.2578125 A 1.0001 1.0001 0 0 0 12.005859 3 z " /> diff --git a/webui/icons/CaptureMultiOn.svg b/webui/icons/CaptureMultiOn.svg index a321812..a300176 100644 --- a/webui/icons/CaptureMultiOn.svg +++ b/webui/icons/CaptureMultiOn.svg @@ -43,8 +43,8 @@ inkscape:pagecheckerboard="false" showguides="true" inkscape:zoom="22.627417" - inkscape:cx="-0.72920387" - inkscape:cy="7.8002717" + inkscape:cx="10.16466" + inkscape:cy="9.4354561" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" @@ -63,7 +63,7 @@ visible="true" /> + d="M 11.984375 3 C 11.941061 3.0008129 11.897959 3.0051276 11.853516 3.0117188 C 10.286646 3.2438475 8.136946 4.0574533 6.2988281 5.7988281 C 4.4607104 7.5402031 3 10.247626 3 14 C 3.0000552 14.552261 3.4477387 14.999945 4 15 L 12 15 C 12.009951 14.999998 12.019433 14.998331 12.029297 14.998047 C 11.395157 16.392601 11 18.054836 11 20 C 11.000055 20.552261 11.447739 20.999945 12 21 L 20 21 C 20.679123 20.999892 21.160657 20.337413 20.951172 19.691406 C 19.780536 16.081947 19.7839 14.693655 20.966797 10.257812 C 21.151395 9.5630514 20.564604 8.9062558 19.853516 9.0117188 C 18.286646 9.2438477 16.136946 10.057454 14.298828 11.798828 C 13.789668 12.28119 13.311031 12.84037 12.882812 13.472656 C 11.784853 10.027645 11.808678 8.6007383 12.966797 4.2578125 C 13.139855 3.6064764 12.634083 2.9878063 11.984375 3 z " /> diff --git a/webui/src/icons/CaptureMulti.tsx b/webui/src/icons/CaptureMulti.tsx index 89ada5d..9c478a1 100644 --- a/webui/src/icons/CaptureMulti.tsx +++ b/webui/src/icons/CaptureMulti.tsx @@ -1,5 +1,5 @@ // autogenerated from icon svg file "icons/CaptureMulti.svg", do not edit; import * as React from 'react'; import { SvgIcon, SvgIconProps } from '@mui/material'; -export const CaptureMultiIcon = (props: SvgIconProps) => ; +export const CaptureMultiIcon = (props: SvgIconProps) => ; export default CaptureMultiIcon; \ No newline at end of file diff --git a/webui/src/icons/CaptureMultiOn.tsx b/webui/src/icons/CaptureMultiOn.tsx index 225d8b5..2ca06ba 100644 --- a/webui/src/icons/CaptureMultiOn.tsx +++ b/webui/src/icons/CaptureMultiOn.tsx @@ -1,5 +1,5 @@ // autogenerated from icon svg file "icons/CaptureMultiOn.svg", do not edit; import * as React from 'react'; import { SvgIcon, SvgIconProps } from '@mui/material'; -export const CaptureMultiOnIcon = (props: SvgIconProps) => ; +export const CaptureMultiOnIcon = (props: SvgIconProps) => ; export default CaptureMultiOnIcon; \ No newline at end of file From d9a303c87ea3e468a01fee0e6b883e48cd9020ca Mon Sep 17 00:00:00 2001 From: thediveo Date: Tue, 13 Feb 2024 16:00:28 +0100 Subject: [PATCH 7/7] doc: multi-interface capture selection Signed-off-by: thediveo --- webui/src/views/help/chapters/Capture.mdx | 39 +++++++++++++++++++-- webui/src/views/help/chapters/Ghostwire.mdx | 5 +++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/webui/src/views/help/chapters/Capture.mdx b/webui/src/views/help/chapters/Capture.mdx index edb1245..80f316b 100644 --- a/webui/src/views/help/chapters/Capture.mdx +++ b/webui/src/views/help/chapters/Capture.mdx @@ -1,4 +1,9 @@ import CaptureIcon from 'icons/Capture' +import CaptureMultiIcon from 'icons/CaptureMulti' +import CaptureMultiOnIcon from 'icons/CaptureMultiOn' +import CaptureCheckIcon from 'icons/CaptureCheck' +import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank' +import FullscreenIcon from "@mui/icons-material/Fullscreen" import RefreshIcon from '@mui/icons-material/Refresh' import { ContaineeBadge } from 'components/containeebadge' import { NifBadge } from 'components/nifbadge' @@ -19,9 +24,11 @@ plugin](https://github.com/siemens/cshargextcap/releases) installed. Live captures allow you to see network packets in "real time" (albeit with some delay due to transmitting them from the capture host to your desktop Wireshark). -No more first having to do start and later stop some "blind" capturing and -downloading a capture file afterwards – just to find out that you got it wrong, -so you need to rinse and repeat, hoping it'll work this time. +No more "blind capturing" and downloading huge pcap files – just to find out +that you got it wrong, so you need to rinse and repeat, hoping it'll work this +time. + +## One-Click Capture To start live captures, simply click on one of the capture buttons. @@ -41,6 +48,31 @@ traffic differs as follows: | | captures from all containers of this particular pod – *please see note below*. | | | captures only from this particular network interface. | +## Selective Multi-Interface Capture + +If you want to selectively capture from a subset of network interfaces of a +virtual IP stack/network namespace, first tap or click on a multi capture button (next to a details zoom button). Notice how it turns active, displaying now as +. Tap or click this button again to leave +mult-interface selection mode. + +While in multi-interface selection mode, the capture buttons next to network +interfaces are replaced by / checkboxes. You can now +check the network interfaces you want to capture from in the same live capture +session. + +To start a live capture session, touch or click the capture button that is now visible right next to the +multi-interface selection button . Please +note this capture button will be disabled as long as no network interface has +been selected. + +To leave the multi-interface selection mode, tap or click the + button. It then turns into + back again. + ## Notes #### Live Capture Wireshark Plugin and Service @@ -52,6 +84,7 @@ To use live packet capture you'll need: **Wireshark™ version 3.0.2** or later installed on your client system. Supported systems are: - Linux x86 64 bit and ARM 64 bit, + - macos x86 64 bit and ARM 64 bit, - Windows x86 64 bit only. - on the server side you'll need the [Packetflix live capture streaming diff --git a/webui/src/views/help/chapters/Ghostwire.mdx b/webui/src/views/help/chapters/Ghostwire.mdx index a71efa6..c9066b2 100644 --- a/webui/src/views/help/chapters/Ghostwire.mdx +++ b/webui/src/views/help/chapters/Ghostwire.mdx @@ -51,6 +51,11 @@ plugin](https://github.com/siemens/cshargextcap/releases) installed. You can install this plugin at any time without the need to restart your browser (Chrome/Chromium, ...). +- capture from all network interfaces of a container/containee, +- capture from a single specific network interface, +- capture from multiple, selected network interfaces of the same + container/containee. + ## Copyright The [Edgeshark project](https://github.com/siemens/edgeshark) is (c) Siemens