Skip to content

Commit

Permalink
Feature/filter (#43)
Browse files Browse the repository at this point in the history
* wip: filter input component for containees list

Signed-off-by: thediveo <[email protected]>

* wip: add proper filter clear button

Signed-off-by: thediveo <[email protected]>

* wip: poc filtering the wiring view

Signed-off-by: thediveo <[email protected]>

* wip: filter containees list in sidebar

Signed-off-by: thediveo <[email protected]>

* wip: show filter only for breadboard view; show error state for input field for invalid regexps

Signed-off-by: thediveo <[email protected]>

* wip: hotkey, input focus, close on enter

Signed-off-by: thediveo <[email protected]>

* wip: use hotkey ^f instead of ^s; disable spell checkar on regexp, sherly!

Signed-off-by: thediveo <[email protected]>

* wip: support details filtering; display notice when filtering is being applied

Signed-off-by: thediveo <[email protected]>

* doc: update integrated help

Signed-off-by: thediveo <[email protected]>

---------

Signed-off-by: thediveo <[email protected]>
  • Loading branch information
thediveo authored Feb 11, 2024
1 parent 4f351ae commit f7b9a72
Show file tree
Hide file tree
Showing 19 changed files with 649 additions and 72 deletions.
64 changes: 64 additions & 0 deletions webui/icons/Case.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 64 additions & 0 deletions webui/icons/Regexp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions webui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"process": "^0.11.10",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^v5.0.0-1",
"react-inlinesvg": "^3.0.2",
"react-router-dom": "^6.21.2",
"react-scripts": "^5.0.1",
Expand Down Expand Up @@ -70,6 +71,7 @@
},
"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",
"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",
Expand Down
54 changes: 44 additions & 10 deletions webui/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT

import React, { useRef } from 'react'
import { BrowserRouter as Router, Route, Routes, useMatch, Navigate} from 'react-router-dom'
import { BrowserRouter as Router, Route, Routes, useMatch, Navigate } from 'react-router-dom'

import { basename } from 'utils/basename'

Expand All @@ -25,6 +25,7 @@ import {
Tooltip,
Typography,
useMediaQuery,
Grid,
} from '@mui/material';

import { gwDarkTheme, gwLightTheme } from './appstyles'
Expand All @@ -43,7 +44,7 @@ import OpenHouseIcon from 'icons/views/OpenHouse'

import { About as AboutView } from 'views/about'
import { Help as HelpView } from 'views/help'
import { Settings as SettingsView, showEmptyNetnsAtom, themeAtom, THEME_DARK, THEME_USERPREF } from 'views/settings'
import { Settings as SettingsView, showEmptyNetnsAtom, themeAtom, THEME_DARK, THEME_USERPREF, filterPatternAtom, filterCaseSensitiveAtom, filterRegexpAtom } from 'views/settings'
import { Everything as EverythingView } from 'views/everything'
import { NetnsWiring } from 'views/netnswiring'
import { NetnsDetails as NetnsDetailsView } from 'views/netnsdetails'
Expand All @@ -57,6 +58,7 @@ import { BrandIcon } from 'components/brandicon'
import { useDynVars } from 'components/dynvars'
import { ScreenShooter, useScreenShooterModal } from 'components/screenshooter'
import OpenHouse from 'views/openhouse/OpenHouse'
import { FilterInput, FilterPattern } from 'components/filterinput'


const SettingsViewIcon = SettingsIcon
Expand All @@ -72,16 +74,19 @@ const refresherIntervals = [
]

/**
* The `LxGhostwireApp` component renders the general app layout without
* thinking about providers for routing, themes, discovery, et cetera. So this
* component deals with:
* The `GhostwireApp` component renders the general app layout without thinking
* about providers for routing, themes, discovery, et cetera. So this component
* deals with:
* - app bar with title, number of namespaces badge, quick actions.
* - drawer for navigating the different views and types of namespaces.
* - scrollable content area.
*/
const GhostwireApp = () => {

const [showEmptyNetns] = useAtom(showEmptyNetnsAtom)
const [filterPattern, setFilterPattern] = useAtom(filterPatternAtom)
const [filterCase, setFilterCase] = useAtom(filterCaseSensitiveAtom)
const [filterRegexp, setFilterRegexp] = useAtom(filterRegexpAtom)

// Determine the number of discovered network namespaces, as well as the
// number of shown network namespaces: depending on filter settings and
Expand All @@ -106,7 +111,9 @@ const GhostwireApp = () => {
const nmatch1 = useMatch('/n')
const nmatch2 = useMatch('/n/:slug')
const isDetails = nmatch1 !== null || nmatch2 != null


const canFilter = wmatch1 !== null || nmatch1 !== null

const listContainees = isWiring || isDetails

const alsoSnapshotable = useMatch('/lochla') !== null
Expand All @@ -115,10 +122,16 @@ const GhostwireApp = () => {
const enableSnapshot = listContainees || alsoSnapshotable

// What to capture, if any.
const snapshotRef = useRef<HTMLDivElement|null>(null)
const snapshotRef = useRef<HTMLDivElement | null>(null)

const setModal = useScreenShooterModal()

const onFilterChangeHandler = (fp: FilterPattern) => {
if (filterPattern != fp.pattern) setFilterPattern(fp.pattern)
if (filterCase != fp.isCaseSensitive) setFilterCase(fp.isCaseSensitive)
if (filterRegexp != fp.isRegexp) setFilterRegexp(fp.isRegexp)
}

useScrollToHash(scrollIdIntoView)

return (
Expand Down Expand Up @@ -150,7 +163,7 @@ const GhostwireApp = () => {
<Brand />
</Typography>
</>}
drawer={closeDrawer => <>
drawer={(closeDrawer, focusRef) => <>
<List onClick={closeDrawer}>
<DrawerLinkItem
key="wiring"
Expand Down Expand Up @@ -194,7 +207,28 @@ const GhostwireApp = () => {
{listContainees && <>
<Divider />
<List
subheader={<ListSubheader>Containees</ListSubheader>}
subheader={<ListSubheader onClick={(event) => {
event.stopPropagation()
event.preventDefault()
}}>
<Grid container direction="column">
<Grid item>Containees</Grid>
{ canFilter &&
<Grid item>
<FilterInput
focusRef={focusRef}
filterPattern={{
pattern: filterPattern,
isCaseSensitive: filterCase,
isRegexp: filterRegexp,
}}
onChange={onFilterChangeHandler}
onEnter={closeDrawer}
/>
</Grid>
}
</Grid>
</ListSubheader>}
onClick={closeDrawer}
>
<ContaineeNavigator
Expand All @@ -209,7 +243,7 @@ const GhostwireApp = () => {
{/* main content area */}
<Box m={0} flex={1} overflow="auto">
<Routes>
<Route path="/w/:slug" element={<NetnsDetailsView ref={snapshotRef}/>} />
<Route path="/w/:slug" element={<NetnsDetailsView ref={snapshotRef} />} />
<Route path="/w" element={<NetnsWiring ref={snapshotRef} />} />
<Route path="/n/:slug" element={<NetnsDetailsView ref={snapshotRef} />} />
<Route path="/n" element={<EverythingView ref={snapshotRef} />} />
Expand Down
30 changes: 27 additions & 3 deletions webui/src/components/appbardrawer/AppBarDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
//
// SPDX-License-Identifier: MIT

import React, { useState } from 'react'
import React, { useEffect, useRef, useState } from 'react'

import MenuIcon from '@mui/icons-material/Menu'
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
import { AppBar, Box, Divider, IconButton, styled, SwipeableDrawer, Theme, Toolbar, useTheme } from '@mui/material'
import { useHotkeys } from 'react-hotkeys-hook'


// Width of drawer.
Expand Down Expand Up @@ -74,7 +75,7 @@ export interface AppBarDrawerProps {
* want to close the drawer whenever the user clicks on them in order to
* navigate to a different route.
*/
drawer?: (drawerCloser: drawerCloser) => React.ReactNode
drawer?: (drawerCloser: drawerCloser, focusRef?: React.RefObject<HTMLDivElement>) => React.ReactNode
/**
* optionally sets the width in pixels of the drawer. Defaults to 240 pixels
* if unspecified.
Expand Down Expand Up @@ -124,6 +125,29 @@ const AppBarDrawer = ({
// Not much state here in ... Denmark?!
const [drawerOpen, setDrawerOpen] = useState(false)

// We need to get hold onto the filter pattern input field in order to
// autofocus upon opening the drawer. Below, we pass it to the drawer
// rendering function, so it can pass it down even further into whatever
// HTML it want to set the focus on.
const focusRef = useRef<HTMLDivElement>(null)

// Register hotkey to open the drawer and put the focus on the containee
// filter pattern input, if any.
useHotkeys(['/', 'ctrl+f'], (e) => {
e.preventDefault()
e.stopPropagation()
setDrawerOpen(true)
}, { useKey: true })

// When the drawer opens, and if there is an HTML element reference then set
// the focus to this HTML element. This will be the containee filter pattern
// input field, where enabled depending on view.
useEffect(() => {
if (!drawerOpen) return
if (!focusRef.current) return
focusRef.current.focus()
}, [drawerOpen])

// Convenience handlers for dealing with the swipeable drawer, that should
// keep users busy on a rainy Sunday afternoon.
const openDrawer = () => { setDrawerOpen(true) }
Expand Down Expand Up @@ -169,7 +193,7 @@ const AppBarDrawer = ({
</IconButton>
</DrawerHeader>
<Divider />
{drawer && drawer(closeDrawer)}
{drawer && drawer(closeDrawer, focusRef)}
</SwappyDrawer>
</>
}
Expand Down
22 changes: 19 additions & 3 deletions webui/src/components/containeenavigator/ContaineeNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { ContaineeIcon } from 'utils/containeeicon'
import { useContextualId } from 'components/idcontext'
import PrivilegedIcon from 'icons/containeestates/Privileged'
import CapableIcon from 'icons/containeestates/Capable'
import { useAtom } from 'jotai'
import { filterCaseSensitiveAtom, filterPatternAtom, filterRegexpAtom } from 'views/settings'
import { getFilterFn } from 'components/filterinput'


const ContaineeItem = styled(ListItemButton)<RouterLinkProps>(({ theme }) => ({
Expand Down Expand Up @@ -176,6 +179,7 @@ export interface ContaineeNavigatorProps {
* navigator will not show any containees of such empty network namespaces.
*/
export const ContaineeNavigator = ({ allnetns, filterEmpty, nolink }: ContaineeNavigatorProps) => {

const domIdBase = useContextualId('')

const match1 = useMatch('/:view')
Expand All @@ -184,14 +188,26 @@ export const ContaineeNavigator = ({ allnetns, filterEmpty, nolink }: ContaineeN

const inDetails = !!match && !!(match.params as { [key: string]: string })['details']

const [filterPattern] = useAtom(filterPatternAtom)
const [filterCase] = useAtom(filterCaseSensitiveAtom)
const [filterRegexp] = useAtom(filterRegexpAtom)
const filterfn = getFilterFn({
pattern: filterPattern,
isCaseSensitive: filterCase,
isRegexp: filterRegexp,
})

// Get all pods and those pesky primitive containees that aren't pot'ed. In
// case a containee should appear multiple times, it is attached to multiple
// network namespaces and we're fine with that.
const allContainees = Object.values(allnetns)
.filter(netns => !emptyNetns(netns) || !filterEmpty)
.map(netns =>
(netns.pods as Containee[]).concat(
netns.containers.filter(primcontainee => !isPodContainer(primcontainee))))
.map(netns => {
const primcontainees = netns.containers.filter(primcntee => filterfn(primcntee.name))
const pods = netns.pods.filter(pod => filterfn(pod.name))
return (pods as Containee[]).concat(
primcontainees.filter(primcontainee => !isPodContainer(primcontainee)))
})
.flat() // bang all namespace-local containees into a single flat list.

// Prepare the item list from the containees where this list now replaces
Expand Down
Loading

0 comments on commit f7b9a72

Please sign in to comment.