Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solution for paginating the Discover page (#496) #525

Merged
merged 14 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
"start:mgmtApp": "yarn workspace @hawtio/online-management-api-app start",
"build:mgmtApi": "yarn workspace @hawtio/online-management-api build",
"start:k8s": "yarn build:k8s && yarn start:k8sApp",
"analyze:k8s": "yarn build:k8s && yarn analyze:k8sApp",
"build:k8s": "yarn build:oAuthApi && yarn build:k8sApi",
"start:k8sApp": "yarn workspace @hawtio/online-kubernetes-api-app start",
"analyze:k8sApp": "yarn workspace @hawtio/online-kubernetes-api-app analyze",
"build:k8sApi": "yarn workspace @hawtio/online-kubernetes-api build",
"start:oAuth": "yarn build:oAuthApi && yarn start:oAuthApp",
"start:oAuthApp": "yarn workspace @hawtio/online-oauth-app start",
Expand All @@ -42,6 +44,7 @@
"gen:proxying": "./scripts/generate-proxying.sh",
"gen:serving": "./scripts/generate-serving.sh",
"test": "yarn workspaces foreach -v -Aipt --exclude @hawtio/online-root --exclude \"@hawtio/*-app\" run test",
"test:watch": "yarn workspaces foreach -v -Aipt --exclude @hawtio/online-root --exclude \"@hawtio/*-app\" run test:watch",
"deploy:k8s:namespace": "./scripts/kube-apply.sh deploy/k8s/namespace/",
"deploy:k8s:cluster": "./scripts/kube-apply.sh deploy/k8s/cluster/",
"deploy:openshift:namespace": "./scripts/kube-apply.sh deploy/openshift/namespace/",
Expand Down
3 changes: 2 additions & 1 deletion packages/kubernetes-api-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"start": "webpack serve --hot --progress --config webpack.config.js",
"build": "webpack --config webpack.config.js",
"test": "jest --watchAll=false --passWithNoTests src/**/*.test.ts*",
"test:coverage": "yarn test --coverage"
"test:coverage": "yarn test --coverage",
"analyze": "webpack --mode development --analyze --progress --config webpack.config.js"
},
"dependencies": {
"@hawtio/online-kubernetes-api": "workspace:*",
Expand Down
139 changes: 110 additions & 29 deletions packages/kubernetes-api-app/src/Kubernetes.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { isK8ApiRegistered, k8Api, k8Service } from '@hawtio/online-kubernetes-api'
import React, { useRef, useEffect, useState } from 'react'
import {
Alert,
Expand All @@ -20,17 +19,34 @@ import {
MastheadContent,
Label,
Button,
Tabs,
Tab,
TabTitleText,
NumberInput,
} from '@patternfly/react-core'
import { InfoCircleIcon } from '@patternfly/react-icons'
import { KubernetesClient } from './KubernetesClient'
import { useUser, userService } from '@hawtio/react'
import {
K8Actions,
isK8ApiRegistered,
k8Api,
k8Service,
KubeProject,
KubePodsByProject,
} from '@hawtio/online-kubernetes-api'
import { KubernetesProjectPods } from './KubernetesProjectPods'
import { KubernetesProjects } from './KubernetesProjects'

export const Kubernetes: React.FunctionComponent = () => {
const timerRef = useRef<NodeJS.Timeout | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<Error | null>()
const { username, userLoaded } = useUser()

const [projects, setProjects] = useState<KubeProject[]>([])
const [podsByProject, setPodsByProject] = useState<KubePodsByProject>({})
const [activeTabKey, setActiveTabKey] = React.useState<string | number>(0)

useEffect(() => {
setIsLoading(true)

Expand All @@ -50,7 +66,18 @@ export const Kubernetes: React.FunctionComponent = () => {

if (k8Service.hasError()) {
setError(k8Service.error)
return
}

k8Service.on(K8Actions.CHANGED, () => {
const projects = k8Service.getProjects()
setProjects([...projects]) // must use spread to ensure update

// Ensure that update is carried out on nested objects
// by using assign to create new object
const podsByProject: KubePodsByProject = k8Service.getPods()
setPodsByProject(Object.assign({}, podsByProject))
})
}

checkLoading()
Expand Down Expand Up @@ -93,6 +120,19 @@ export const Kubernetes: React.FunctionComponent = () => {
)
}

const handleTabClick = (
event: React.MouseEvent<unknown> | React.KeyboardEvent | MouseEvent,
tabIndex: string | number,
) => {
setActiveTabKey(tabIndex)
}

const onChangeNSLimit = (event: React.FormEvent<HTMLInputElement>) => {
const value = (event.target as HTMLInputElement).value
const nsLimit: number = value === '' ? 3 : +value
k8Service.namespaceLimit = nsLimit
}

return (
<Card>
<CardTitle>
Expand All @@ -119,39 +159,80 @@ export const Kubernetes: React.FunctionComponent = () => {
</Masthead>

<Panel>
<PanelHeader>API Properties</PanelHeader>
<PanelHeader>
<Title headingLevel='h1'>Kubernetes Client</Title>
</PanelHeader>
<Divider />
<PanelMain>
<PanelMainBody>
<DescriptionList isHorizontal>
<DescriptionListGroup>
<DescriptionListTerm>Kubernetes Master</DescriptionListTerm>
<DescriptionListDescription>{k8Api.masterUri()}</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>Is Openshift?</DescriptionListTerm>
<DescriptionListDescription>{k8Api.isOpenshift ? 'true' : 'false'}</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>Cluster Console</DescriptionListTerm>
<DescriptionListDescription>
{k8Api.consoleUri ? <a href={k8Api.consoleUri}>{k8Api.consoleUri}</a> : '<not found>'}
</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>Kubernetes Config</DescriptionListTerm>
<DescriptionListDescription>
<pre>{JSON.stringify(k8Api.oAuthProfile, null, 2)}</pre>
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
<Panel>
<PanelHeader>
<Title headingLevel='h4'>Pods in Namespace Limit</Title>
</PanelHeader>
<PanelMain>
<PanelMainBody>
<NumberInput
inputName='Pods in Namespace Limit'
unit='Pods'
inputAriaLabel='Pods in Namespace Limit NumberInput'
minusBtnAriaLabel='NSPodsMinus1'
plusBtnAriaLabel='NSPodsPlus1'
value={k8Service.namespaceLimit}
onMinus={() => (k8Service.namespaceLimit = k8Service.namespaceLimit - 1)}
onPlus={() => (k8Service.namespaceLimit = k8Service.namespaceLimit + 1)}
onChange={onChangeNSLimit}
/>
</PanelMainBody>
</PanelMain>
</Panel>

<Divider />

<Tabs activeKey={activeTabKey} onSelect={handleTabClick} isBox>
<Tab eventKey={0} title={<TabTitleText>API Properties</TabTitleText>}>
<Panel>
<PanelMain>
<PanelMainBody>
<DescriptionList isHorizontal>
<DescriptionListGroup>
<DescriptionListTerm>Kubernetes Master</DescriptionListTerm>
<DescriptionListDescription>{k8Api.masterUri()}</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>Is Openshift?</DescriptionListTerm>
<DescriptionListDescription>
{k8Api.isOpenshift ? 'true' : 'false'}
</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>Cluster Console</DescriptionListTerm>
<DescriptionListDescription>
{k8Api.consoleUri ? <a href={k8Api.consoleUri}>{k8Api.consoleUri}</a> : '<not found>'}
</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>Kubernetes Config</DescriptionListTerm>
<DescriptionListDescription>
<pre>{JSON.stringify(k8Api.oAuthProfile, null, 2)}</pre>
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</PanelMainBody>
</PanelMain>
</Panel>
</Tab>
<Tab eventKey={1} title={<TabTitleText>Pods</TabTitleText>}>
<KubernetesProjectPods podsByProject={podsByProject} />
</Tab>
{projects.length > 0 && (
<Tab eventKey={2} title={<TabTitleText>Projects</TabTitleText>}>
<KubernetesProjects projects={projects} />
</Tab>
)}
</Tabs>
</PanelMainBody>
</PanelMain>
</Panel>

<Divider />

<KubernetesClient />
</CardBody>
</Card>
)
Expand Down
52 changes: 0 additions & 52 deletions packages/kubernetes-api-app/src/KubernetesClient.tsx

This file was deleted.

118 changes: 118 additions & 0 deletions packages/kubernetes-api-app/src/KubernetesPaginatedPods.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React from 'react'
import {
Button,
DescriptionList,
DescriptionListDescription,
DescriptionListGroup,
DescriptionListTerm,
EmptyState,
EmptyStateBody,
EmptyStateVariant,
Panel,
PanelHeader,
PanelMain,
PanelMainBody,
Title,
Toolbar,
ToolbarContent,
ToolbarItem,
} from '@patternfly/react-core'
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'
import { KubePod, k8Service } from '@hawtio/online-kubernetes-api'

type KubePagePodsProps = {
project: string
pods: KubePod[]
}

export const KubernetesPaginatedPods: React.FunctionComponent<KubePagePodsProps> = (props: KubePagePodsProps) => {
const prevPods = () => {
// Should refresh from 2 components up
k8Service.previous(props.project)
}

const nextPods = () => {
// Should refresh from 2 components up
k8Service.next(props.project)
}

return (
<Panel isScrollable>
<PanelHeader>
<Toolbar id='pagination-toolbar-items' className='paginated-pods-toolbar-content' isSticky>
<ToolbarContent>
<ToolbarItem>
<Button variant='control' onClick={() => prevPods()} isDisabled={!k8Service.hasPrevious(props.project)}>
&lt;&lt; Previous
</Button>
</ToolbarItem>
<ToolbarItem>
<Button variant='control' onClick={() => nextPods()} isDisabled={!k8Service.hasNext(props.project)}>
Next &gt;&gt;
</Button>
</ToolbarItem>
</ToolbarContent>
</Toolbar>
</PanelHeader>
<PanelMain>
<PanelMainBody>
{props.pods.length === 0 && (
<EmptyState variant={EmptyStateVariant.xs}>
<Title headingLevel='h4' size='md'>
No jolokia pods found
</Title>
<EmptyStateBody>Pods were retrieved but none have a jolokia port.</EmptyStateBody>
</EmptyState>
)}

{props.pods.length > 0 && (
<Table key={props.project} aria-label='Pods table' variant='compact'>
<Thead>
<Tr>
<Th>Name</Th>
<Th>Namespace</Th>
<Th>Labels</Th>
<Th>Annotations</Th>
<Th>Status</Th>
</Tr>
</Thead>
<Tbody>
{props.pods.map(pod => (
<Tr key={pod.metadata?.uid}>
<Td dataLabel='Name'>{pod.metadata?.name}</Td>
<Td dataLabel='Namespace'>{pod.metadata?.namespace}</Td>
<Td dataLabel='Labels'>
<DescriptionList>
{Object.entries(pod.metadata?.labels || {}).map(([key, value]) => {
return (
<DescriptionListGroup key={key}>
<DescriptionListTerm>{key}</DescriptionListTerm>
<DescriptionListDescription>{value as string}</DescriptionListDescription>
</DescriptionListGroup>
)
})}
</DescriptionList>
</Td>
<Td dataLabel='Annotations'>
<DescriptionList>
{Object.entries(pod.metadata?.annotations || {}).map(([key, value]) => {
return (
<DescriptionListGroup key={key}>
<DescriptionListTerm>{key}</DescriptionListTerm>
<DescriptionListDescription>{value as string}</DescriptionListDescription>
</DescriptionListGroup>
)
})}
</DescriptionList>
</Td>
<Td dataLabel='Status'>{k8Service.podStatus(pod)}</Td>
</Tr>
))}
</Tbody>
</Table>
)}
</PanelMainBody>
</PanelMain>
</Panel>
)
}
Loading