Skip to content

Commit

Permalink
feat(springboot-plugin) Add Info and Health pages
Browse files Browse the repository at this point in the history
  • Loading branch information
mmelko committed Nov 16, 2023
1 parent 1898cb0 commit 8dbc221
Show file tree
Hide file tree
Showing 9 changed files with 342 additions and 105 deletions.
165 changes: 145 additions & 20 deletions packages/hawtio/src/plugins/springboot/Health.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,152 @@
import React from 'react'
import { Card, CardBody, CardHeader, Grid, GridItem, PageSection, Title } from '@patternfly/react-core'
import React, { useEffect, useState } from 'react'
import {
Card,
CardBody,
CardHeader,
Flex,
FlexItem,
Grid,
GridItem,
gridSpans,
PageSection,
Title,
} from '@patternfly/react-core'
import { loadHealth } from '@hawtiosrc/plugins/springboot/springboot-service'
import { HealthComponentDetail, HealthData } from '@hawtiosrc/plugins/springboot/types'
import { TableComposable, Tbody, Td, Tr } from '@patternfly/react-table'
import { humanizeLabels } from '@hawtiosrc/util/strings'
import { ChartDonutUtilization } from '@patternfly/react-charts'
import {
CheckCircleIcon,
ExclamationCircleIcon,
ExclamationTriangleIcon,
InfoCircleIcon,
QuestionCircleIcon,
} from '@patternfly/react-icons'

const componentSpanRecord: Record<string, number[]> = {
diskSpace: [7, 2],
ping: [5, 0],
camelHealth: [5, 1],
camel: [5, 1],
}
const ComponentDetails: React.FunctionComponent<{ componentDetails: HealthComponentDetail[] }> = ({
componentDetails,
}) => {
return (
<TableComposable variant='compact' borders={false}>
<Tbody style={{ fontSize: 'xx-small' }}>
{componentDetails.map((detail, index) => {
return (
<Tr key={'row' + detail.key + index}>
<Td key={detail.key + index}>{humanizeLabels(detail.key)}:</Td>
<Td>
{typeof detail.value === 'string' ? detail.value : <ComponentDetails componentDetails={detail.value} />}
</Td>
</Tr>
)
})}
</Tbody>
</TableComposable>
)
}
const HealthStatusIcon: React.FunctionComponent<{ status: string }> = ({ status }) => {
switch (status) {
case 'UP':
return <CheckCircleIcon color={'green'} />
case 'DOWN':
return <ExclamationCircleIcon color={'red'} />
case 'OUT_OF_SERVICE':
return <ExclamationTriangleIcon color={'orange'} />
case 'UNKNOWN':
return <QuestionCircleIcon />
default:
return <InfoCircleIcon color={'blue'} />
}
}

const DiskComponentDetails: React.FunctionComponent<{ componentDetails: HealthComponentDetail[] }> = ({
componentDetails,
}) => {
const total = Number.parseInt(componentDetails.find(k => k.key === 'total')!.value as string)
const free = Number.parseInt(componentDetails.find(k => k.key === 'free')!.value as string)
const usedPercentage = Math.round(((total - free) * 100) / total)

return (
<Grid height={'100%'}>
<GridItem span={6}>
<ComponentDetails componentDetails={componentDetails} />
</GridItem>
<GridItem span={6}>
<ChartDonutUtilization
ariaDesc='Storage capacity'
ariaTitle='Donut utilization chart example'
constrainToVisibleArea
data={{ x: 'Used Space', y: usedPercentage }}
name='chart2'
subTitle='of available space'
title={`${usedPercentage}% used`}
thresholds={[{ value: 90 }]}
width={435}
/>
</GridItem>
</Grid>
)
}
export const Health: React.FunctionComponent = () => {
loadHealth()
const [healthData, setHealthData] = useState<HealthData>()

useEffect(() => {
loadHealth().then(healthData => {
setHealthData(healthData)
})
}, [])

return (
<PageSection variant='light'>
<Grid hasGutter span={12}>
<GridItem>
<Card>
<CardHeader>
<Title headingLevel='h2'>System</Title>
</CardHeader>
<CardBody></CardBody>
</Card>
<Card>
<CardHeader>
<Title headingLevel='h2'>System</Title>
</CardHeader>
<CardBody></CardBody>
</Card>
</GridItem>
</Grid>
<PageSection variant='default'>
{healthData && (
<Grid hasGutter span={4}>
<GridItem span={12}>
<Card>
<CardHeader>
<Flex>
<HealthStatusIcon status={healthData?.status} />
<Title headingLevel='h3'>
<span>Overall status: {healthData?.status}</span>
</Title>
</Flex>
</CardHeader>
</Card>
</GridItem>
{healthData?.components.map(component => (
<GridItem
span={componentSpanRecord[component.name]![0] as gridSpans}
key={component.name}
mdRowSpan={componentSpanRecord[component.name]![1] as gridSpans}
>
<Card>
<CardHeader>
<Title headingLevel='h3'>{humanizeLabels(component.name!)}</Title>
</CardHeader>
<CardBody>
<Flex>
<FlexItem>
<HealthStatusIcon status={component.status} />
</FlexItem>
<FlexItem>Status: {component.status}</FlexItem>
{component.details &&
(component.name === 'diskSpace' ? (
<DiskComponentDetails componentDetails={component.details} />
) : (
<ComponentDetails componentDetails={component.details} />
))}
</Flex>
</CardBody>
</Card>
</GridItem>
))}
</Grid>
)}
</PageSection>
)
}
45 changes: 38 additions & 7 deletions packages/hawtio/src/plugins/springboot/Info.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
import React from "react"
import {PageSection} from "@patternfly/react-core"
import React, { useEffect, useState } from 'react'
import { FormGroup, PageSection } from '@patternfly/react-core'
import { getInfo } from '@hawtiosrc/plugins/springboot/springboot-service'
import { TableComposable, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'

export const Info:React.FunctionComponent = () => (
<PageSection variant='light'>
Info - TO BE DONE
</PageSection>
)
export const Info: React.FunctionComponent = () => {
const [systemProperties, setSystemProperties] = useState<{ key: string; value: string }[]>([])

useEffect(() => {
getInfo().then(res => {
setSystemProperties(res)
})
}, [])

return (
<PageSection variant='light'>
<FormGroup>
<TableComposable aria-label='Message Table' variant='compact' height='80vh' isStriped isStickyHeader>
<Thead>
<Tr>
<Th data-testid={'name-header'}>Property Name</Th>
<Th data-testid={'value-header'}>Property Value</Th>
</Tr>
</Thead>
<Tbody>
{systemProperties.map((prop, index) => {
return (
<Tr key={'row' + index} data-testid={'row' + index}>
<Td style={{ width: '20%' }}>{prop.key}</Td>
<Td style={{ flex: 3 }}>{prop.value}</Td>
</Tr>
)
})}
</Tbody>
</TableComposable>
</FormGroup>
</PageSection>
)
}
72 changes: 72 additions & 0 deletions packages/hawtio/src/plugins/springboot/SpringBoot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Nav, NavItem, NavList, PageGroup, PageNavigation, PageSection, Title } from '@patternfly/react-core'
import React, { useEffect, useState } from 'react'

import { Navigate, NavLink, Route, Routes, useLocation } from 'react-router-dom'
import { Health } from '@hawtiosrc/plugins/springboot/Health'
import { Info } from '@hawtiosrc/plugins/springboot/Info'
import { Loggers } from '@hawtiosrc/plugins/springboot/Loggers'
import { Trace } from '@hawtiosrc/plugins/springboot/Trace'
import { hasEndpoint } from '@hawtiosrc/plugins/springboot/springboot-service'

type NavItem = {
id: string
title: string
component: JSX.Element
}
export const SpringBoot: React.FunctionComponent = () => {
const location = useLocation()
const [navItems, setNavItems] = useState<NavItem[]>([])

useEffect(() => {
const initNavItems = async () => {
const nav: NavItem[] = []
if (await hasEndpoint('Health')) {
nav.push({ id: 'health', title: 'Health', component: <Health /> })
}

if (await hasEndpoint('Info')) {
nav.push({ id: 'info', title: 'Info', component: <Info /> })
}

if (await hasEndpoint('Loggers')) {
nav.push({ id: 'loggers', title: 'Loggers', component: <Loggers /> })
}

if (await hasEndpoint('Trace')) {
nav.push({ id: 'trace', title: 'Trace', component: <Trace /> })
}

setNavItems([...nav])
}
initNavItems()
}, [])

return (
<React.Fragment>
<PageSection variant='light'>
<Title headingLevel='h1'>Spring Boot</Title>
</PageSection>
<PageGroup>
<PageNavigation>
<Nav aria-label='Spring-boot Nav' variant='tertiary'>
<NavList>
{navItems.map(navItem => (
<NavItem key={navItem.id} isActive={location.pathname === `/springboot/${navItem.id}`}>
<NavLink to={navItem.id}>{navItem.title}</NavLink>
</NavItem>
))}
</NavList>
</Nav>
</PageNavigation>
</PageGroup>
<PageSection>
<Routes>
{navItems.map(navItem => (
<Route key={navItem.id} path={navItem.id} element={navItem.component} />
))}
<Route path='/' element={<Navigate to='health' />} />
</Routes>
</PageSection>
</React.Fragment>
)
}
54 changes: 0 additions & 54 deletions packages/hawtio/src/plugins/springboot/Springboot.tsx

This file was deleted.

10 changes: 3 additions & 7 deletions packages/hawtio/src/plugins/springboot/Trace.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import React from "react"
import {PageSection} from "@patternfly/react-core"
import React from 'react'
import { PageSection } from '@patternfly/react-core'

export const Trace:React.FunctionComponent = () => (
<PageSection variant='light'>
Trace - TO BE DONE
</PageSection>
)
export const Trace: React.FunctionComponent = () => <PageSection variant='light'>Trace - TO BE DONE</PageSection>
4 changes: 2 additions & 2 deletions packages/hawtio/src/plugins/springboot/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ Displays the current health status of the application together with details retu

### Info

Displays relevant information regarding the Spring boot application. It is the output of the Info actuator endpoint.
Displays relevant information regarding the Spring boot application. It is the output of the Info actuator endpoint.

### Loggers

Lists all the available loggers in the application. You can modify the level of a logger and the changes will take effect immediately.
Lists all the available loggers in the application. You can modify the level of a logger and the changes will take effect immediately.

### Trace

Expand Down
10 changes: 5 additions & 5 deletions packages/hawtio/src/plugins/springboot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { pluginId, pluginPath } from './globals'
import { workspace } from '@hawtiosrc/plugins'
import { helpRegistry } from '@hawtiosrc/help'
import help from './help.md'
import {Springboot} from "@hawtiosrc/plugins/springboot/Springboot"
import { SpringBoot } from '@hawtiosrc/plugins/springboot/SpringBoot'

export const springboot: HawtioPlugin = () => {
hawtio.addPlugin({
id: pluginId,
title: 'Springboot',
title: 'Spring Boot',
path: pluginPath,
component: Springboot,
isActive: async () => workspace.hasMBeans(),
component: SpringBoot,
isActive: async () => workspace.treeContainsDomainAndProperties('org.springframework.boot'),
})
helpRegistry.add(pluginId, 'Runtime', help, 16)
helpRegistry.add(pluginId, 'Spring Boot', help, 17)
}
Loading

0 comments on commit 8dbc221

Please sign in to comment.