diff --git a/packages/hawtio/src/plugins/SelectionNodeData.ts b/packages/hawtio/src/plugins/SelectionNodeData.ts new file mode 100644 index 000000000..1fcb5c3ee --- /dev/null +++ b/packages/hawtio/src/plugins/SelectionNodeData.ts @@ -0,0 +1,49 @@ +import { AttributeValues } from './connect/jolokia-service' +import { MBeanNode } from './shared' +import { attributeService } from './shared/attributes/attribute-service' + +export type MBeanAttributeData = { + nodeData: MBeanAttributes | void + children: { + [id: string]: MBeanAttributes | void + } +} + +export type MBeanChartData = { + nodeData: MBeanChartDataEntries | void + children: { + [id: string]: MBeanChartDataEntries | void + } +} + +export type MBeanAttributes = { + node: MBeanNode + data: AttributeValues +} + +export type MBeanChartDataEntries = { + node: MBeanNode + entries: MBeanChartDataEntriesTime[] +} + +export type MBeanChartDataEntriesTime = { + time: number + data: AttributeValues[] +} + +async function getAttributesForSpecificNode(node: MBeanNode): Promise { + if (!node || !node?.objectName) return Promise.resolve(null) + + return Promise.resolve({ node, data: await attributeService.read(node.objectName) }) +} + +export async function getAttributesForNode(node: MBeanNode | null): Promise { + if (!node) return Promise.resolve({ nodeData: undefined, children: {} }) + + return { + nodeData: (await getAttributesForSpecificNode(node)) ?? undefined, + children: Object.fromEntries( + await Promise.all(node.getChildren().map(async child => [child.id, await getAttributesForSpecificNode(child)])), + ), + } +} diff --git a/packages/hawtio/src/plugins/jmx/JmxContent.tsx b/packages/hawtio/src/plugins/jmx/JmxContent.tsx index 5c360eb21..3aeddd0bd 100644 --- a/packages/hawtio/src/plugins/jmx/JmxContent.tsx +++ b/packages/hawtio/src/plugins/jmx/JmxContent.tsx @@ -1,4 +1,6 @@ import { + Card, + CardBody, EmptyState, EmptyStateIcon, EmptyStateVariant, @@ -14,18 +16,27 @@ import { } from '@patternfly/react-core' import './JmxContent.css' import { CubesIcon } from '@patternfly/react-icons' -import React, { useContext } from 'react' +import React, { FunctionComponent, useContext } from 'react' import { NavLink, Navigate, Route, Routes, useLocation } from 'react-router-dom' -import { MBeanTreeContext } from './context' import { Chart } from '@hawtiosrc/plugins/shared/chart' import { Operations } from '@hawtiosrc/plugins/shared/operations' import { Attributes, AttributeTable } from '@hawtiosrc/plugins/shared/attributes' import { JmxContentMBeans, MBeanNode } from '@hawtiosrc/plugins/shared' +import { PluginNodeSelectionContext } from '../selectionNodeContext' +import { isObject } from '@hawtiosrc/util/objects' export const JmxContent: React.FunctionComponent = () => { - const { selectedNode } = useContext(MBeanTreeContext) + const { selectedNode, selectedNodeAttributes, isReadingAttributes } = useContext(PluginNodeSelectionContext) const { pathname, search } = useLocation() + const isReadingAttributesCard: FunctionComponent = () => ( + + + Reading attributes... + + + ) + if (!selectedNode) { return ( @@ -39,12 +50,15 @@ export const JmxContent: React.FunctionComponent = () => { ) } - const mBeanApplicable = (node: MBeanNode) => Boolean(node.objectName) - const mBeanCollectionApplicable = (node: MBeanNode) => Boolean(node.children?.every(child => child.objectName)) + const loadingAttributes = (node: MBeanNode) => isReadingAttributes + const mBeanApplicable = (node: MBeanNode) => isObject(selectedNodeAttributes.nodeData) + const mBeanCollectionApplicable = (node: MBeanNode) => + Object.values(selectedNodeAttributes.children).every(nodeAttributes => isObject(nodeAttributes)) const ALWAYS = (node: MBeanNode) => true const tableSelector: (node: MBeanNode) => React.FunctionComponent = (node: MBeanNode) => { const tablePriorityList: { condition: (node: MBeanNode) => boolean; element: React.FunctionComponent }[] = [ + { condition: loadingAttributes, element: isReadingAttributesCard }, { condition: mBeanApplicable, element: Attributes }, { condition: mBeanCollectionApplicable, element: AttributeTable }, ] diff --git a/packages/hawtio/src/plugins/selectionNodeContext.ts b/packages/hawtio/src/plugins/selectionNodeContext.ts index 45233d998..3c99a1fda 100644 --- a/packages/hawtio/src/plugins/selectionNodeContext.ts +++ b/packages/hawtio/src/plugins/selectionNodeContext.ts @@ -1,17 +1,34 @@ -import { createContext, useState } from 'react' +import { createContext, useEffect, useState } from 'react' import { MBeanNode } from '@hawtiosrc/plugins/shared' +import { getAttributesForNode, MBeanAttributeData } from './SelectionNodeData' /** * Custom React hook for using JMX MBean tree. */ export function usePluginNodeSelected() { const [selectedNode, setSelectedNode] = useState(null) - return { selectedNode, setSelectedNode } + const [selectedNodeAttributes, setSelectedNodeAttributes] = useState({ + nodeData: undefined, + children: {}, + }) + const [isReadingAttributes, setIsReadingAttributes] = useState(false) + + useEffect(() => { + ;(async () => { + setIsReadingAttributes(true) + setSelectedNodeAttributes(await getAttributesForNode(selectedNode)) + setIsReadingAttributes(false) + })() + }, [selectedNode]) + + return { selectedNode, setSelectedNode, selectedNodeAttributes, isReadingAttributes } } type PluginNodeSelectionContext = { selectedNode: MBeanNode | null setSelectedNode: (selectedNode: MBeanNode | null) => void + selectedNodeAttributes: MBeanAttributeData + isReadingAttributes: boolean } export const PluginNodeSelectionContext = createContext({ @@ -19,4 +36,6 @@ export const PluginNodeSelectionContext = createContext { /* no-op */ }, + selectedNodeAttributes: { nodeData: undefined, children: {} }, + isReadingAttributes: false, }) diff --git a/packages/hawtio/src/plugins/shared/attributes/AttributeTable.tsx b/packages/hawtio/src/plugins/shared/attributes/AttributeTable.tsx index 20bb1dee4..5ef59816f 100644 --- a/packages/hawtio/src/plugins/shared/attributes/AttributeTable.tsx +++ b/packages/hawtio/src/plugins/shared/attributes/AttributeTable.tsx @@ -1,54 +1,25 @@ -import { useContext, useEffect, useState } from 'react' -import { Card, CardBody, Text } from '@patternfly/react-core' +import { useContext } from 'react' +import { Card } from '@patternfly/react-core' import { Table, TableBody, TableHeader, TableProps } from '@patternfly/react-table' import { PluginNodeSelectionContext } from '@hawtiosrc/plugins/selectionNodeContext' import { AttributeValues } from '@hawtiosrc/plugins/connect/jolokia-service' -import { attributeService } from './attribute-service' import './AttributeTable.css' import { JmxContentMBeans } from '@hawtiosrc/plugins/shared/JmxContentMBeans' import { humanizeLabels } from '@hawtiosrc/util/strings' +import { MBeanAttributes } from '@hawtiosrc/plugins/SelectionNodeData' +import { isObject } from '@hawtiosrc/util/objects' export const AttributeTable: React.FunctionComponent = () => { - const { selectedNode } = useContext(PluginNodeSelectionContext) - const [attributesList, setAttributesList] = useState([{}]) - const [isReading, setIsReading] = useState(false) + const { selectedNode, selectedNodeAttributes } = useContext(PluginNodeSelectionContext) - useEffect(() => { - if (!selectedNode) { - return - } - - const readAttributes = async () => { - const childrenMbeansAttributes: AttributeValues[] = [] - setIsReading(true) - if (selectedNode.children) - for (const mbean of selectedNode.children) { - const objectName = mbean.objectName - if (objectName) { - const attrs = await attributeService.read(objectName) - childrenMbeansAttributes.push(attrs) - } - } - setAttributesList([...childrenMbeansAttributes]) - setIsReading(false) - } - readAttributes() - }, [selectedNode]) + const attributesList: AttributeValues[] = Object.values(selectedNodeAttributes.children) + .filter((mbeanAttribute): mbeanAttribute is MBeanAttributes => isObject(mbeanAttribute)) + .map(mbeanAttribute => mbeanAttribute.data) if (!selectedNode) { return null } - if (isReading) { - return ( - - - Reading attributes... - - - ) - } - const checkIfAllMBeansHaveSameAttributes = (attributesList: AttributeValues[]) => { if (attributesList.length <= 1) { return true diff --git a/packages/hawtio/src/plugins/shared/attributes/Attributes.tsx b/packages/hawtio/src/plugins/shared/attributes/Attributes.tsx index 0a3c33350..c9ef45dd8 100644 --- a/packages/hawtio/src/plugins/shared/attributes/Attributes.tsx +++ b/packages/hawtio/src/plugins/shared/attributes/Attributes.tsx @@ -1,67 +1,22 @@ -import { AttributeValues } from '@hawtiosrc/plugins/connect/jolokia-service' import { isObject } from '@hawtiosrc/util/objects' import { Card, CardBody, Text } from '@patternfly/react-core' import { InfoCircleIcon } from '@patternfly/react-icons' import { OnRowClick, Table, TableBody, TableHeader, TableProps } from '@patternfly/react-table' -import { IResponse } from 'jolokia.js' -import { useEffect, useState, useContext } from 'react' +import { useState, useContext } from 'react' import { PluginNodeSelectionContext } from '@hawtiosrc/plugins/selectionNodeContext' -import { log } from '../globals' -import { attributeService } from './attribute-service' import { AttributeModal } from './AttributeModal' export const Attributes: React.FunctionComponent = () => { - const { selectedNode } = useContext(PluginNodeSelectionContext) - const [attributes, setAttributes] = useState({}) - const [isReading, setIsReading] = useState(true) + const { selectedNode, selectedNodeAttributes } = useContext(PluginNodeSelectionContext) const [isModalOpen, setIsModalOpen] = useState(false) const [selected, setSelected] = useState({ name: '', value: '' }) - useEffect(() => { - if (!selectedNode || !selectedNode.mbean || !selectedNode.objectName) { - return - } - - setIsReading(true) - const objectName = selectedNode.objectName - const readAttributes = async () => { - const attrs = await attributeService.read(objectName) - setAttributes(attrs) - setIsReading(false) - } - readAttributes() - }, [selectedNode]) - - useEffect(() => { - if (!selectedNode || !selectedNode.mbean || !selectedNode.objectName) { - return - } - - const mbean = selectedNode.objectName - attributeService.register({ type: 'read', mbean }, (response: IResponse) => { - log.debug('Scheduler - Attributes:', response.value) - setAttributes(response.value as AttributeValues) - }) - - return () => attributeService.unregisterAll() - }, [selectedNode]) - if (!selectedNode || !selectedNode.mbean || !selectedNode.objectName) { return null } - if (isReading) { - return ( - - - Reading attributes... - - - ) - } - const columns: TableProps['cells'] = ['Attribute', 'Value'] - const rows: TableProps['rows'] = Object.entries(attributes).map(([name, value]) => [ + const rows: TableProps['rows'] = Object.entries(selectedNodeAttributes?.nodeData?.data ?? {}).map(([name, value]) => [ name, isObject(value) ? JSON.stringify(value) : String(value), ]) diff --git a/packages/hawtio/src/ui/page/HawtioPage.tsx b/packages/hawtio/src/ui/page/HawtioPage.tsx index 649f2d076..fbdd296e9 100644 --- a/packages/hawtio/src/ui/page/HawtioPage.tsx +++ b/packages/hawtio/src/ui/page/HawtioPage.tsx @@ -29,7 +29,7 @@ export const HawtioPage: React.FunctionComponent = () => { const { plugins, pluginsLoaded } = usePlugins() const navigate = useNavigate() const { search } = useLocation() - const { selectedNode, setSelectedNode } = usePluginNodeSelected() + const { selectedNode, setSelectedNode, selectedNodeAttributes, isReadingAttributes } = usePluginNodeSelected() if (!userLoaded || !pluginsLoaded) { return @@ -72,7 +72,9 @@ export const HawtioPage: React.FunctionComponent = () => { defaultManagedSidebarIsOpen={showVerticalNavByDefault} > {/* Provider for handling selected node shared between the plugins */} - + {/* plugins */} {plugins.map(plugin => (