diff --git a/src/fireedge/package-lock.json b/src/fireedge/package-lock.json
index 78750f9153..a5d33760b5 100644
--- a/src/fireedge/package-lock.json
+++ b/src/fireedge/package-lock.json
@@ -92,6 +92,7 @@
"socket.io": "4.4.1",
"socket.io-client": "4.4.1",
"speakeasy": "2.0.0",
+ "split-grid": "1.0.11",
"sprintf-js": "1.1.2",
"style-loader": "3.2.1",
"terser-webpack-plugin": "5.1.4",
@@ -11544,6 +11545,11 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/split-grid": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/split-grid/-/split-grid-1.0.11.tgz",
+ "integrity": "sha512-ELtFtxc3r5we5GZfe6Fi0BFFxIi2M6BY1YEntBscKRDD3zx4JVHqx2VnTRSQu1BixCYSTH3MTjKd4esI2R7EgQ=="
+ },
"node_modules/sprintf-js": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
@@ -21752,6 +21758,11 @@
"base32.js": "0.0.1"
}
},
+ "split-grid": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/split-grid/-/split-grid-1.0.11.tgz",
+ "integrity": "sha512-ELtFtxc3r5we5GZfe6Fi0BFFxIi2M6BY1YEntBscKRDD3zx4JVHqx2VnTRSQu1BixCYSTH3MTjKd4esI2R7EgQ=="
+ },
"sprintf-js": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
diff --git a/src/fireedge/package.json b/src/fireedge/package.json
index f4b0c126dd..7222e40033 100644
--- a/src/fireedge/package.json
+++ b/src/fireedge/package.json
@@ -126,6 +126,7 @@
"socket.io": "4.4.1",
"socket.io-client": "4.4.1",
"speakeasy": "2.0.0",
+ "split-grid": "1.0.11",
"sprintf-js": "1.1.2",
"style-loader": "3.2.1",
"terser-webpack-plugin": "5.1.4",
diff --git a/src/fireedge/src/client/components/SplitPane/index.js b/src/fireedge/src/client/components/SplitPane/index.js
index 0ded71ed06..dcb27bf7a7 100644
--- a/src/fireedge/src/client/components/SplitPane/index.js
+++ b/src/fireedge/src/client/components/SplitPane/index.js
@@ -13,130 +13,117 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState, createRef, useRef, useEffect } from 'react'
+import { useRef, useEffect, useCallback, Children, ReactElement } from 'react'
import PropTypes from 'prop-types'
-import { Divider } from '@mui/material'
-import makeStyles from '@mui/styles/makeStyles'
+import Split, { SplitOptions } from 'split-grid'
+import { styled, Divider } from '@mui/material'
-const useStyles = makeStyles((theme) => ({
- splitPane: {
- display: 'flex',
- flexDirection: 'column',
- height: '100%',
- },
- topPane: {
- flex: 1,
- },
- separator: {
- position: 'relative',
- cursor: 'row-resize',
+const Gutter = styled(Divider)(({ theme, direction = 'row' }) => ({
+ position: 'relative',
+ cursor: `${direction}-resize`,
+ height: 8,
+ marginBlock: '1em',
+ background: `linear-gradient(
+ to right,
+ transparent,
+ ${theme.palette.divider},
+ transparent
+ )`,
+ '&:after': {
+ content: "''",
+ position: 'absolute',
+ zIndex: 1,
+ left: '50%',
+ width: 8,
height: 8,
- marginBlock: '1em',
- background: `linear-gradient(
- to right,
- transparent,
- ${theme.palette.divider},
- transparent
- )`,
- '&:after': {
- content: "''",
- position: 'absolute',
- zIndex: 1,
- left: '50%',
- width: 8,
- height: 8,
- transform: 'rotate(45deg)',
- backgroundColor: theme.palette.action.active,
- },
+ transform: 'rotate(45deg)',
+ backgroundColor: theme.palette.action.active,
},
}))
-const SplitPane = ({ children, containerProps }) => {
- const classes = useStyles()
- const [topHeight, setTopHeight] = useState(null)
+/**
+ * @typedef SplitGridHook
+ * @property {function():object} getGridProps - Function to get grid props
+ * @property {ReactElement} GutterComponent - Gutter component
+ */
- const splitPaneRef = createRef()
- const topRef = createRef()
- const separatorYPosition = useRef(null)
+/**
+ * Hook to create a split pane with a divider between the two panes.
+ *
+ * @param {SplitOptions} options - Options to configure the split pane
+ * @returns {SplitGridHook} Hook functions to element grid and gutter
+ */
+const useSplitGrid = (options) => {
+ const {
+ columnMinSizes,
+ rowMinSizes,
+ columnMaxSizes,
+ rowMaxSizes,
+ gridTemplateColumns,
+ gridTemplateRows,
+ } = options
- const onMouseDown = (event) => {
- separatorYPosition.current = event?.touches?.[0]?.clientY ?? event.clientY
- }
-
- const onMouseMove = (event) => {
- if (!separatorYPosition.current) return
-
- const clientY = event?.touches?.[0]?.clientY || event.clientY
-
- const newTopHeight = topHeight + clientY - separatorYPosition.current
- separatorYPosition.current = clientY
-
- if (newTopHeight <= 0) {
- return topHeight !== 0 && setTopHeight(0)
- }
-
- const splitPaneHeight = splitPaneRef.current?.clientHeight
-
- if (newTopHeight >= splitPaneHeight) {
- return topHeight !== splitPaneHeight && setTopHeight(splitPaneHeight)
- }
-
- setTopHeight(newTopHeight)
- }
-
- const onMouseUp = () => {
- separatorYPosition.current = null
- }
+ const split = useRef({})
useEffect(() => {
- document.addEventListener('mousemove', onMouseMove)
- document.addEventListener('mouseup', onMouseUp)
- // on mobile device
- document.addEventListener('touchmove', onMouseMove)
- document.addEventListener('touchend', onMouseUp)
+ split.current = Split(options)
- return () => {
- document.removeEventListener('mousemove', onMouseMove)
- document.removeEventListener('mouseup', onMouseUp)
- // on mobile device
- document.removeEventListener('touchmove', onMouseMove)
- document.removeEventListener('touchend', onMouseUp)
- }
- })
+ return () => split.current.destroy()
+ }, [columnMinSizes, rowMinSizes, columnMaxSizes, rowMaxSizes])
- useEffect(() => {
- if (!topHeight && children[1]) {
- setTopHeight(document.body.clientHeight / 2)
- topRef.current.style.flex = 'none'
- }
-
- topRef.current.style.height = children[1]
- ? `${topHeight}px`
- : `${splitPaneRef.current?.clientHeight}px`
- }, [topHeight, children[1]])
-
- return (
-
-
- {children[0]}
-
- {!!children[1] && (
-
- )}
- {children[1]}
-
+ const getGridProps = useCallback(
+ () => ({
+ display: 'grid',
+ height: 1, // 100%
+ gridTemplateColumns,
+ gridTemplateRows,
+ }),
+ [gridTemplateColumns, gridTemplateRows]
)
+
+ const GutterComponent = useCallback(
+ ({ direction, track }) => {
+ const handleDragStart = (e) => {
+ split.current?.handleDragStart(e, direction, track)
+ }
+
+ return (
+
+ )
+ },
+ [split.current?.handleDragStart]
+ )
+
+ return { getGridProps, GutterComponent }
}
-SplitPane.propTypes = {
- children: PropTypes.array.isRequired,
- containerProps: PropTypes.object,
+/**
+ * @param {SplitOptions} props - Component props
+ * @param {Function|ReactElement} [props.children] - Child components
+ * @returns {ReactElement|function(SplitGridHook):ReactElement} Split pane component
+ */
+const SplitGrid = ({ children, ...options }) => {
+ const hook = useSplitGrid(options)
+
+ if (!children) return null
+ if (typeof children === 'function') return children(hook)
+
+ return !(Children.count(children) === 0) ? Children.only(children) : null
}
-export default SplitPane
+SplitGrid.propTypes = {
+ children: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
+ options: PropTypes.object,
+}
+
+export { Gutter, useSplitGrid }
+
+export default SplitGrid
diff --git a/src/fireedge/src/client/containers/Clusters/index.js b/src/fireedge/src/client/containers/Clusters/index.js
index cb6852ef38..72efb9467f 100644
--- a/src/fireedge/src/client/containers/Clusters/index.js
+++ b/src/fireedge/src/client/containers/Clusters/index.js
@@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState } from 'react'
-import { Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { ClustersTable } from 'client/components/Tables'
import ClusterTabs from 'client/components/Tabs/Cluster'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
+/**
+ * Displays a list of Clusters with a split pane between the list and selected row(s).
+ *
+ * @returns {ReactElement} Clusters list and selected row(s)
+ */
function Clusters() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
- return (
-
-
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
-
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
+ return (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
)}
-
+
)}
)
}
+/**
+ * Displays details of a Cluster.
+ *
+ * @param {object} cluster - Cluster to display
+ * @returns {ReactElement} Cluster details
+ */
+const InfoTabs = memo(({ cluster }) => (
+
+
+ {`#${cluster.ID} | ${cluster.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { cluster: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default Clusters
diff --git a/src/fireedge/src/client/containers/Datastores/index.js b/src/fireedge/src/client/containers/Datastores/index.js
index 03905da472..8001b64aba 100644
--- a/src/fireedge/src/client/containers/Datastores/index.js
+++ b/src/fireedge/src/client/containers/Datastores/index.js
@@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState } from 'react'
-import { Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { DatastoresTable } from 'client/components/Tables'
import DatastoreTabs from 'client/components/Tabs/Datastore'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
+/**
+ * Displays a list of Datastores with a split pane between the list and selected row(s).
+ *
+ * @returns {ReactElement} Datastores list and selected row(s)
+ */
function Datastores() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
- return (
-
-
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
-
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
+ return (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
)}
-
+
)}
)
}
+/**
+ * Displays details of a Datastore.
+ *
+ * @param {object} datastore - Datastore to display
+ * @returns {ReactElement} Datastore details
+ */
+const InfoTabs = memo(({ datastore }) => (
+
+
+ {`#${datastore.ID} | ${datastore.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { datastore: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default Datastores
diff --git a/src/fireedge/src/client/containers/Groups/index.js b/src/fireedge/src/client/containers/Groups/index.js
index 43127c3c41..d5348f0577 100644
--- a/src/fireedge/src/client/containers/Groups/index.js
+++ b/src/fireedge/src/client/containers/Groups/index.js
@@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState } from 'react'
-import { Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { GroupsTable } from 'client/components/Tables'
import GroupTabs from 'client/components/Tabs/Group'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
+/**
+ * Displays a list of Groups with a split pane between the list and selected row(s).
+ *
+ * @returns {ReactElement} Groups list and selected row(s)
+ */
function Groups() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
- return (
-
-
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
-
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
+ return (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
)}
-
+
)}
)
}
+/**
+ * Displays details of a Group.
+ *
+ * @param {object} group - Group to display
+ * @returns {ReactElement} Group details
+ */
+const InfoTabs = memo(({ group }) => (
+
+
+ {`#${group.ID} | ${group.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { group: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default Groups
diff --git a/src/fireedge/src/client/containers/Hosts/index.js b/src/fireedge/src/client/containers/Hosts/index.js
index 9c59fe111c..d356e4eccc 100644
--- a/src/fireedge/src/client/containers/Hosts/index.js
+++ b/src/fireedge/src/client/containers/Hosts/index.js
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState } from 'react'
-import { Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { HostsTable } from 'client/components/Tables'
import HostTabs from 'client/components/Tabs/Host'
@@ -23,42 +24,84 @@ import HostActions from 'client/components/Tables/Hosts/actions'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
+/**
+ * Displays a list of Hosts with a split pane between the list and selected row(s).
+ *
+ * @returns {ReactElement} Hosts list and selected row(s)
+ */
function Hosts() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
const actions = HostActions()
- return (
-
-
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
-
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
+ return (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
)}
-
+
)}
)
}
+/**
+ * Displays details of a Host.
+ *
+ * @param {object} host - Host to display
+ * @returns {ReactElement} Host details
+ */
+const InfoTabs = memo(({ host }) => (
+
+
+ {`#${host.ID} | ${host.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { host: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default Hosts
diff --git a/src/fireedge/src/client/containers/Images/index.js b/src/fireedge/src/client/containers/Images/index.js
index 36e481dc03..543d3c70c8 100644
--- a/src/fireedge/src/client/containers/Images/index.js
+++ b/src/fireedge/src/client/containers/Images/index.js
@@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState } from 'react'
-import { Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { ImagesTable } from 'client/components/Tables'
import ImageTabs from 'client/components/Tabs/Image'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
+/**
+ * Displays a list of Images with a split pane between the list and selected row(s).
+ *
+ * @returns {ReactElement} Images list and selected row(s)
+ */
function Images() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
- return (
-
-
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
-
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
+ return (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
)}
-
+
)}
)
}
+/**
+ * Displays details of an Image.
+ *
+ * @param {object} image - Image to display
+ * @returns {ReactElement} Image details
+ */
+const InfoTabs = memo(({ image }) => (
+
+
+ {`#${image.ID} | ${image.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { image: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default Images
diff --git a/src/fireedge/src/client/containers/MarketplaceApps/index.js b/src/fireedge/src/client/containers/MarketplaceApps/index.js
index e511c387e4..8e23c2eb11 100644
--- a/src/fireedge/src/client/containers/MarketplaceApps/index.js
+++ b/src/fireedge/src/client/containers/MarketplaceApps/index.js
@@ -13,8 +13,10 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { useState, JSXElementConstructor } from 'react'
-import { Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { MarketplaceAppsTable } from 'client/components/Tables'
import MarketplaceAppActions from 'client/components/Tables/MarketplaceApps/actions'
@@ -23,46 +25,83 @@ import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
- * Displays a Marketplace Apps list.
+ * Displays a list of Marketplace Apps with a split pane between the list and selected row(s).
*
- * @returns {JSXElementConstructor} List of Marketplace Apps
+ * @returns {ReactElement} Marketplace Apps list and selected row(s)
*/
function MarketplaceApps() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
const actions = MarketplaceAppActions()
- return (
-
-
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
-
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
+ return (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
)}
-
+
)}
)
}
+/**
+ * Displays details of a Marketplace App.
+ *
+ * @param {object} app - Marketplace App to display
+ * @returns {ReactElement} Marketplace App details
+ */
+const InfoTabs = memo(({ app }) => (
+
+
+ {`#${app.ID} | ${app.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { app: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default MarketplaceApps
diff --git a/src/fireedge/src/client/containers/Marketplaces/index.js b/src/fireedge/src/client/containers/Marketplaces/index.js
index b08b76a0ec..b7d2f65c45 100644
--- a/src/fireedge/src/client/containers/Marketplaces/index.js
+++ b/src/fireedge/src/client/containers/Marketplaces/index.js
@@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState } from 'react'
-import { Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { MarketplacesTable } from 'client/components/Tables'
import MarketplaceTabs from 'client/components/Tabs/Marketplace'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
+/**
+ * Displays a list of Marketplaces with a split pane between the list and selected row(s).
+ *
+ * @returns {ReactElement} Marketplaces list and selected row(s)
+ */
function Marketplaces() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
- return (
-
-
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
-
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
+ return (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
)}
-
+
)}
)
}
+/**
+ * Displays details of a Marketplace.
+ *
+ * @param {object} market - Marketplace to display
+ * @returns {ReactElement} Marketplace details
+ */
+const InfoTabs = memo(({ market }) => (
+
+
+ {`#${market.ID} | ${market.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { market: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default Marketplaces
diff --git a/src/fireedge/src/client/containers/Users/index.js b/src/fireedge/src/client/containers/Users/index.js
index f2d4b782ae..78cd6c3e4b 100644
--- a/src/fireedge/src/client/containers/Users/index.js
+++ b/src/fireedge/src/client/containers/Users/index.js
@@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState } from 'react'
-import { Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { UsersTable } from 'client/components/Tables'
import UserTabs from 'client/components/Tabs/User'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
+/**
+ * Displays a list of Users with a split pane between the list and selected row(s).
+ *
+ * @returns {ReactElement} Users list and selected row(s)
+ */
function Users() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
- return (
-
-
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
-
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
+ return (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
)}
-
+
)}
)
}
+/**
+ * Displays details of an User.
+ *
+ * @param {object} user - User to display
+ * @returns {ReactElement} User details
+ */
+const InfoTabs = memo(({ user }) => (
+
+
+ {`#${user.ID} | ${user.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { user: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default Users
diff --git a/src/fireedge/src/client/containers/VNetworkTemplates/index.js b/src/fireedge/src/client/containers/VNetworkTemplates/index.js
index 04d9230cc6..e210dab5c1 100644
--- a/src/fireedge/src/client/containers/VNetworkTemplates/index.js
+++ b/src/fireedge/src/client/containers/VNetworkTemplates/index.js
@@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState } from 'react'
-import { Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { VNetworkTemplatesTable } from 'client/components/Tables'
import VNetworkTemplateTabs from 'client/components/Tabs/VNetworkTemplate'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
+/**
+ * Displays a list of VNet Templates with a split pane between the list and selected row(s).
+ *
+ * @returns {ReactElement} VNet Templates list and selected row(s)
+ */
function VNetworkTemplates() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
- return (
-
-
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
-
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
+ return (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
)}
-
+
)}
)
}
+/**
+ * Displays details of a VNet Template.
+ *
+ * @param {object} vnTemplate - VNet Template to display
+ * @returns {ReactElement} VNet Template details
+ */
+const InfoTabs = memo(({ vnTemplate }) => (
+
+
+ {`#${vnTemplate.ID} | ${vnTemplate.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { vnTemplate: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default VNetworkTemplates
diff --git a/src/fireedge/src/client/containers/VirtualMachines/index.js b/src/fireedge/src/client/containers/VirtualMachines/index.js
index f6b171d847..8ce078f1bf 100644
--- a/src/fireedge/src/client/containers/VirtualMachines/index.js
+++ b/src/fireedge/src/client/containers/VirtualMachines/index.js
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState } from 'react'
-import { Typography, Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { VmsTable } from 'client/components/Tables'
import VmActions from 'client/components/Tables/Vms/actions'
@@ -23,46 +24,84 @@ import VmTabs from 'client/components/Tabs/Vm'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
+/**
+ * Displays a list of VMs with a split pane between the list and selected row(s).
+ *
+ * @returns {ReactElement} VMs list and selected row(s)
+ */
function VirtualMachines() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
const actions = VmActions()
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
+
return (
-
-
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
<>
-
- {`#${selectedRows[0]?.original.ID} | ${selectedRows[0]?.original.NAME}`}
-
-
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
>
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
)}
-
+
)}
)
}
+/**
+ * Displays details of a VM.
+ *
+ * @param {object} vm - VM to display
+ * @returns {ReactElement} VM details
+ */
+const InfoTabs = memo(({ vm }) => (
+
+
+ {`#${vm.ID} | ${vm.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { vm: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default VirtualMachines
diff --git a/src/fireedge/src/client/containers/VirtualNetworks/index.js b/src/fireedge/src/client/containers/VirtualNetworks/index.js
index 631417113f..8db0e9448c 100644
--- a/src/fireedge/src/client/containers/VirtualNetworks/index.js
+++ b/src/fireedge/src/client/containers/VirtualNetworks/index.js
@@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState } from 'react'
-import { Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { VNetworksTable } from 'client/components/Tables'
import VNetworkTabs from 'client/components/Tabs/VNetwork'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
+/**
+ * Displays a list of Virtual Networks with a split pane between the list and selected row(s).
+ *
+ * @returns {ReactElement} Virtual Networks list and selected row(s)
+ */
function VirtualNetworks() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
- return (
-
-
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
-
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
+ return (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
)}
-
+
)}
)
}
+/**
+ * Displays details of a Virtual Network.
+ *
+ * @param {object} vnet - Virtual Network to display
+ * @returns {ReactElement} Virtual Network details
+ */
+const InfoTabs = memo(({ vnet }) => (
+
+
+ {`#${vnet.ID} | ${vnet.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { vnet: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default VirtualNetworks
diff --git a/src/fireedge/src/client/containers/VmTemplates/index.js b/src/fireedge/src/client/containers/VmTemplates/index.js
index 609863b6d2..9918fdfe34 100644
--- a/src/fireedge/src/client/containers/VmTemplates/index.js
+++ b/src/fireedge/src/client/containers/VmTemplates/index.js
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState } from 'react'
-import { Typography, Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { VmTemplatesTable } from 'client/components/Tables'
import VmTemplateActions from 'client/components/Tables/VmTemplates/actions'
@@ -23,46 +24,84 @@ import VmTemplateTabs from 'client/components/Tabs/VmTemplate'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
+/**
+ * Displays a list of VM Templates with a split pane between the list and selected row(s).
+ *
+ * @returns {ReactElement} VM Templates list and selected row(s)
+ */
function VmTemplates() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
const actions = VmTemplateActions()
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
+
return (
-
-
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
<>
-
- {`#${selectedRows[0]?.original.ID} | ${selectedRows[0]?.original.NAME}`}
-
-
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
>
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
)}
-
+
)}
)
}
+/**
+ * Displays details of a VM Template.
+ *
+ * @param {object} vmTemplate - VM Template to display
+ * @returns {ReactElement} VM Template details
+ */
+const InfoTabs = memo(({ vmTemplate }) => (
+
+
+ {`#${vmTemplate.ID} | ${vmTemplate.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { vmTemplate: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default VmTemplates
diff --git a/src/fireedge/src/client/containers/Zones/index.js b/src/fireedge/src/client/containers/Zones/index.js
index f32079ef01..9080c2af74 100644
--- a/src/fireedge/src/client/containers/Zones/index.js
+++ b/src/fireedge/src/client/containers/Zones/index.js
@@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-/* eslint-disable jsdoc/require-jsdoc */
-import { useState } from 'react'
-import { Stack, Chip } from '@mui/material'
+import { ReactElement, useState, memo } from 'react'
+import PropTypes from 'prop-types'
+import { Typography, Box, Stack, Chip } from '@mui/material'
+import { Row } from 'react-table'
import { ZonesTable } from 'client/components/Tables'
import ZoneTabs from 'client/components/Tabs/Zone'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
+/**
+ * Displays a list of Zones with a split pane between the list and selected row(s).
+ *
+ * @returns {ReactElement} Zones list and selected row(s)
+ */
function Zones() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
- return (
-
-
+ const hasSelectedRows = selectedRows?.length > 0
+ const moreThanOneSelected = selectedRows?.length > 1
+ const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
- {selectedRows?.length > 0 && (
-
- {selectedRows?.length === 1 ? (
-
- ) : (
-
- (
- toggleRowSelected(false)}
- />
- )
- )}
- />
-
+ return (
+
+ {({ getGridProps, GutterComponent }) => (
+
+
+
+ {hasSelectedRows && (
+ <>
+
+ {moreThanOneSelected ? (
+
+ ) : (
+
+ )}
+ >
)}
-
+
)}
)
}
+/**
+ * Displays details of a Zone.
+ *
+ * @param {object} zone - Zone to display
+ * @returns {ReactElement} Zone details
+ */
+const InfoTabs = memo(({ zone }) => (
+
+
+ {`#${zone.ID} | ${zone.NAME}`}
+
+
+
+))
+
+InfoTabs.propTypes = { zone: PropTypes.object.isRequired }
+InfoTabs.displayName = 'InfoTabs'
+
+/**
+ * Displays a list of tags that represent the selected rows.
+ *
+ * @param {Row[]} tags - Row(s) to display as tags
+ * @returns {ReactElement} List of tags
+ */
+const GroupedTags = memo(({ tags = [] }) => (
+
+ (
+ toggleRowSelected(false)}
+ />
+ ))}
+ />
+
+))
+
+GroupedTags.propTypes = { tags: PropTypes.array }
+GroupedTags.displayName = 'GroupedTags'
+
export default Zones