From 645ce994ebdc13a7f9d51767c504a688035a43b2 Mon Sep 17 00:00:00 2001 From: mmelko Date: Thu, 7 Sep 2023 07:27:45 +0200 Subject: [PATCH 1/9] feat: Create Runtime plugin with the System Properties tab --- packages/hawtio/src/plugins/index.ts | 2 + .../hawtio/src/plugins/runtime/Metrics.tsx | 3 + .../hawtio/src/plugins/runtime/Runtime.tsx | 63 +++++ .../hawtio/src/plugins/runtime/SysProps.tsx | 228 ++++++++++++++++++ .../hawtio/src/plugins/runtime/Threads.tsx | 3 + .../hawtio/src/plugins/runtime/globals.ts | 6 + packages/hawtio/src/plugins/runtime/help.md | 15 ++ packages/hawtio/src/plugins/runtime/index.ts | 17 ++ .../src/plugins/runtime/runtime-service.ts | 13 + 9 files changed, 350 insertions(+) create mode 100644 packages/hawtio/src/plugins/runtime/Metrics.tsx create mode 100644 packages/hawtio/src/plugins/runtime/Runtime.tsx create mode 100644 packages/hawtio/src/plugins/runtime/SysProps.tsx create mode 100644 packages/hawtio/src/plugins/runtime/Threads.tsx create mode 100644 packages/hawtio/src/plugins/runtime/globals.ts create mode 100644 packages/hawtio/src/plugins/runtime/help.md create mode 100644 packages/hawtio/src/plugins/runtime/index.ts create mode 100644 packages/hawtio/src/plugins/runtime/runtime-service.ts diff --git a/packages/hawtio/src/plugins/index.ts b/packages/hawtio/src/plugins/index.ts index 1c6f2e83..685fe08d 100644 --- a/packages/hawtio/src/plugins/index.ts +++ b/packages/hawtio/src/plugins/index.ts @@ -6,6 +6,7 @@ import { jmx } from './jmx' import { logs } from './logs' import { quartz } from './quartz' import { rbac } from './rbac' +import { runtime } from './runtime' export const registerPlugins: HawtioPlugin = () => { // Auth plugins should be loaded before other plugins @@ -15,6 +16,7 @@ export const registerPlugins: HawtioPlugin = () => { jmx() rbac() camel() + runtime() logs() quartz() } diff --git a/packages/hawtio/src/plugins/runtime/Metrics.tsx b/packages/hawtio/src/plugins/runtime/Metrics.tsx new file mode 100644 index 00000000..9158cc17 --- /dev/null +++ b/packages/hawtio/src/plugins/runtime/Metrics.tsx @@ -0,0 +1,3 @@ +import React from 'react' + +export const Metrics: React.FunctionComponent = () => <>Metrics diff --git a/packages/hawtio/src/plugins/runtime/Runtime.tsx b/packages/hawtio/src/plugins/runtime/Runtime.tsx new file mode 100644 index 00000000..c35f11eb --- /dev/null +++ b/packages/hawtio/src/plugins/runtime/Runtime.tsx @@ -0,0 +1,63 @@ +import { + PageSection, + PageSectionVariants, + NavItem, + Title, + PageGroup, + PageNavigation, + Nav, + NavList, + Card, +} from '@patternfly/react-core' +import React from 'react' + +import { SysProps } from '@hawtiosrc/plugins/runtime/SysProps' +import { Navigate, NavLink, Route, Routes, useLocation } from 'react-router-dom' +import { Metrics } from './Metrics' +import { Threads } from './Threads' + +type NavItem = { + id: string + title: string + component: JSX.Element +} +export const Runtime: React.FunctionComponent = () => { + const location = useLocation() + + const navItems: NavItem[] = [ + { id: 'sysprops', title: 'System Properties', component: }, + { id: 'metrics', title: 'Metrics', component: }, + { id: 'threads', title: 'Threads', component: }, + ] + + return ( + + + Runtime + + + + + + + + + + {navItems.map(navItem => ( + + ))} + } /> + + + + + ) +} diff --git a/packages/hawtio/src/plugins/runtime/SysProps.tsx b/packages/hawtio/src/plugins/runtime/SysProps.tsx new file mode 100644 index 00000000..b21a273c --- /dev/null +++ b/packages/hawtio/src/plugins/runtime/SysProps.tsx @@ -0,0 +1,228 @@ +import React, { useEffect, useState } from 'react' +import { + Bullseye, + Button, + Card, + CardBody, + Dropdown, + DropdownItem, + DropdownToggle, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + FormGroup, + Pagination, + SearchInput, + Toolbar, + ToolbarContent, + ToolbarFilter, + ToolbarGroup, + ToolbarItem, +} from '@patternfly/react-core' +import { TableComposable, Tbody, Td, Th, Thead, ThProps, Tr } from '@patternfly/react-table' +import { SearchIcon } from '@patternfly/react-icons' +import { getSystemProperties, SystemProperty } from '@hawtiosrc/plugins/runtime/runtime-service' +import { objectSorter } from '@hawtiosrc/util/objects' + +export const SysProps: React.FunctionComponent = () => { + const [properties, setProperties] = useState<{ key: string; value: string }[]>([]) + const [filteredProperties, setFilteredProperties] = useState([]) + const [page, setPage] = useState(1) + const [perPage, setPerPage] = useState(20) + const [searchTerm, setSearchTerm] = useState('') + const [filters, setFilters] = useState([]) + const [attributeMenuItem, setAttributeMenuItem] = useState('name') + const [sortByValue, setSortByValue] = React.useState(false) + const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc'>('asc') + const [isDropdownOpen, setIsDropdownOpen] = useState(false) + + useEffect(() => { + getSystemProperties().then(props => { + setProperties(props) + setFilteredProperties(props) + }) + }, []) + + const onDeleteFilter = (filter: string) => { + const newFilters = filters.filter(f => f !== filter) + setFilters(newFilters) + handleSearch(searchTerm, attributeMenuItem, newFilters) + } + + const addToFilters = () => { + setFilters([...filters, `${attributeMenuItem}:${searchTerm}`]) + setSearchTerm('') + } + const clearFilters = () => { + setFilters([]) + setSearchTerm('') + handleSearch('', attributeMenuItem, []) + } + + const PropsPagination = () => { + return ( + setPage(value)} + onPerPageSelect={(_evt, value) => { + setPerPage(value) + setPage(1) + }} + variant='top' + /> + ) + } + + const getPageProperties = (): SystemProperty[] => { + const start = (page - 1) * perPage + const end = start + perPage + return filteredProperties.slice(start, end) + } + const handleSearch = (value: string, attribute: string, filters: string[]) => { + setSearchTerm(value) + //filter with findTerm + let filtered: SystemProperty[] = [] + + if (value === '') { + filtered = [...properties] + } else { + filtered = properties.filter(prop => { + return (attribute === 'name' ? prop.key : prop.value).toLowerCase().includes(value.toLowerCase()) + }) + } + + //filter with the rest of the filters + filters.forEach(value => { + const attr = value.split(':')[0] ?? '' + const searchTerm = value.split(':')[1] ?? '' + filtered = filtered.filter(prop => + (attr === 'name' ? prop.key : prop.value).toLowerCase().includes(searchTerm.toLowerCase()), + ) + }) + + setSearchTerm(value) + setPage(1) + setFilteredProperties([...filtered]) + } + + const attributes = [ + { key: 'name', value: 'Name' }, + { key: 'value', value: 'Value' }, + ] + + const dropdownItems = attributes.map(a => ( + { + setAttributeMenuItem(a.key) + handleSearch(searchTerm, a.key, filters) + }} + key={a.key} + > + {a.value} + + )) + + const getSortParams = (sortByValue: boolean): ThProps['sort'] => ({ + sortBy: { + index: sortByValue ? 1 : 0, + direction: activeSortDirection, + defaultDirection: 'asc', // starting sort direction when first sorting a column. Defaults to 'asc' + }, + onSort: (_event, index, direction) => { + setSortByValue(sortByValue) + setActiveSortDirection(direction) + }, + columnIndex: sortByValue ? 1 : 0, + }) + const sortProperties = (): SystemProperty[] => { + let sortedProps = filteredProperties + sortedProps = filteredProperties.sort((a, b) => { + const aValue = sortByValue ? a.value : a.key + const bValue = sortByValue ? b.value : b.key + return objectSorter(aValue, bValue, activeSortDirection === 'desc') + }) + + return sortedProps + } + return ( + + {/*Browse Messages*/} + + + + + setIsDropdownOpen(false)} + defaultValue='name' + toggle={ + + {attributes.find(att => att.key === attributeMenuItem)?.value} + + } + isOpen={isDropdownOpen} + dropdownItems={dropdownItems} + /> + onDeleteFilter(filter as string)} + deleteChipGroup={clearFilters} + categoryName='Filters' + > + handleSearch(value, attributeMenuItem, filters)} + aria-label='Search input' + /> + + + + + + + + + + + {sortProperties().length > 0 && ( + + + + + Property Name + Property Value + + + + {getPageProperties().map((prop, index) => { + return ( + + {prop.key} + {prop.value} + + ) + })} + + + + )} + {filteredProperties.length === 0 && ( + + + + No results found. + + + )} + + + ) +} diff --git a/packages/hawtio/src/plugins/runtime/Threads.tsx b/packages/hawtio/src/plugins/runtime/Threads.tsx new file mode 100644 index 00000000..f95f72f0 --- /dev/null +++ b/packages/hawtio/src/plugins/runtime/Threads.tsx @@ -0,0 +1,3 @@ +import React from 'react' + +export const Threads: React.FunctionComponent = () => <>Threads diff --git a/packages/hawtio/src/plugins/runtime/globals.ts b/packages/hawtio/src/plugins/runtime/globals.ts new file mode 100644 index 00000000..7bb0ba4c --- /dev/null +++ b/packages/hawtio/src/plugins/runtime/globals.ts @@ -0,0 +1,6 @@ +import { Logger } from '@hawtiosrc/core' + +export const pluginId = 'runtime' +export const pluginName = 'hawtio-runtime' +export const pluginPath = '/runtime' +export const log = Logger.get(pluginName) diff --git a/packages/hawtio/src/plugins/runtime/help.md b/packages/hawtio/src/plugins/runtime/help.md new file mode 100644 index 00000000..7be53131 --- /dev/null +++ b/packages/hawtio/src/plugins/runtime/help.md @@ -0,0 +1,15 @@ +## Runtime + +The Runtime plugin displays information about the JVM runtime. + +### System Properties + +Displays a filterable and sortable list of system properties. + +### Metrics + +Displays runtime metrics from the JVM, such as memory, CPU, garbage collection and more. + +### Threads + +Inspects the threads running in the JVM. diff --git a/packages/hawtio/src/plugins/runtime/index.ts b/packages/hawtio/src/plugins/runtime/index.ts new file mode 100644 index 00000000..5a77d0ad --- /dev/null +++ b/packages/hawtio/src/plugins/runtime/index.ts @@ -0,0 +1,17 @@ +import { hawtio, HawtioPlugin } from '@hawtiosrc/core' +import { Runtime } from './Runtime' +import { pluginId, pluginPath } from './globals' +import { workspace } from '@hawtiosrc/plugins' +import { helpRegistry } from '@hawtiosrc/help' +import help from './help.md' + +export const runtime: HawtioPlugin = () => { + hawtio.addPlugin({ + id: pluginId, + title: 'Runtime', + path: pluginPath, + component: Runtime, + isActive: async () => workspace.hasMBeans(), + }) + helpRegistry.add(pluginId, 'Runtime', help, 15) +} diff --git a/packages/hawtio/src/plugins/runtime/runtime-service.ts b/packages/hawtio/src/plugins/runtime/runtime-service.ts new file mode 100644 index 00000000..d883093a --- /dev/null +++ b/packages/hawtio/src/plugins/runtime/runtime-service.ts @@ -0,0 +1,13 @@ +import { jolokiaService } from '@hawtiosrc/plugins/shared' + +export type SystemProperty = { key: string; value: string } + +export function getSystemProperties(): Promise { + const systemProperties: SystemProperty[] = [] + return jolokiaService.readAttribute('java.lang:type=Runtime', 'SystemProperties').then(attr => { + for (const [k, v] of Object.entries(attr as object)) { + systemProperties.push({ key: k, value: v }) + } + return systemProperties + }) +} From 3db302aea71729268c5bede9400087aa99176d0d Mon Sep 17 00:00:00 2001 From: mmelko Date: Fri, 8 Sep 2023 12:55:54 +0200 Subject: [PATCH 2/9] fix: redirection to system properties tab after the load. Fix showing active tab --- packages/hawtio/src/plugins/runtime/Runtime.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hawtio/src/plugins/runtime/Runtime.tsx b/packages/hawtio/src/plugins/runtime/Runtime.tsx index c35f11eb..1ec91831 100644 --- a/packages/hawtio/src/plugins/runtime/Runtime.tsx +++ b/packages/hawtio/src/plugins/runtime/Runtime.tsx @@ -40,7 +40,7 @@ export const Runtime: React.FunctionComponent = () => {