Skip to content

Commit

Permalink
feat(connect): provide login form for connecting to authenticated rem…
Browse files Browse the repository at this point in the history
…ote jolokia

Fix #482
  • Loading branch information
tadayosi committed Oct 26, 2023
1 parent 8880c93 commit a8907d3
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 74 deletions.
3 changes: 3 additions & 0 deletions packages/hawtio/src/core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ class HawtioCore {
resolved.push(plugin)
}
}

log.debug('Resolved plugins:', resolved)

return resolved
}
}
Expand Down
126 changes: 68 additions & 58 deletions packages/hawtio/src/plugins/connect/Connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
Modal,
ModalVariant,
PageSection,
PageSectionVariants,
Text,
TextContent,
Toolbar,
Expand All @@ -30,78 +29,89 @@ import { ConnectModal } from './ConnectModal'
import { DELETE } from './connections'
import { ConnectContext, useConnections } from './context'
import { log } from './globals'
import { Route, Routes } from 'react-router-dom'
import { ConnectLogin } from './login/ConnectLogin'

export const Connect: React.FunctionComponent = () => {
const { connections, dispatch } = useConnections()
log.debug('Connections:', connections)

const ConnectHint = () => (
<ExpandableSection
displaySize='large'
toggleContent={
<Text>
<OutlinedQuestionCircleIcon /> Hint
</Text>
}
>
<Text component='p'>
This page allows you to connect to remote processes which{' '}
<strong>
already have a{' '}
<a href='https://jolokia.org/agent.html' target='_blank' rel='noreferrer'>
Jolokia agent
</a>{' '}
running inside them
</strong>
. You will need to know the host name, port and path of the Jolokia agent to be able to connect.
</Text>
<Text component='p'>
If the process you wish to connect to does not have a Jolokia agent inside, please refer to the{' '}
<a href='http://jolokia.org/agent.html' target='_blank' rel='noreferrer'>
Jolokia documentation
</a>{' '}
for how to add a JVM, servlet or OSGi based agent inside it.
</Text>
<Text component='p'>
If you are using{' '}
<a href='https://developers.redhat.com/products/fuse/overview/' target='_blank' rel='noreferrer'>
Red Hat Fuse{' '}
</a>
or{' '}
<a href='http://activemq.apache.org/' target='_blank' rel='noreferrer'>
Apache ActiveMQ
</a>
, then a Jolokia agent is included by default (use context path of Jolokia agent, usually
<code>jolokia</code>). Or you can always just deploy hawtio inside the process (which includes the Jolokia
agent, use Jolokia servlet mapping inside hawtio context path, usually <code>hawtio/jolokia</code>).
</Text>
</ExpandableSection>
)

const ConnectionList = () => (
<DataList id='connection-list' aria-label='connection list' isCompact>
{Object.entries(connections).map(([name, connection]) => (
<ConnectionItem key={name} name={name} connection={connection} />
))}
</DataList>
)

return (
<ConnectContext.Provider value={{ connections, dispatch }}>
<PageSection variant={PageSectionVariants.light}>
<PageSection id='connect-header' variant='light'>
<TextContent>
<Text component='h1'>Connect</Text>
<ConnectHint />
</TextContent>
</PageSection>
<PageSection variant={PageSectionVariants.light}>
<ConnectToolbar />
<ConnectionList />
<PageSection id='connect-main'>
<Routes>
<Route key='login' path='login' element={<ConnectLogin />} />
<Route index element={<ConnectContent />} />
</Routes>
</PageSection>
</ConnectContext.Provider>
)
}

const ConnectHint: React.FunctionComponent = () => (
<ExpandableSection
displaySize='large'
toggleContent={
<Text>
<OutlinedQuestionCircleIcon /> Hint
</Text>
}
>
<Text component='p'>
This page allows you to connect to remote processes which{' '}
<strong>
already have a{' '}
<a href='https://jolokia.org/agent.html' target='_blank' rel='noreferrer'>
Jolokia agent
</a>{' '}
running inside them
</strong>
. You will need to know the host name, port and path of the Jolokia agent to be able to connect.
</Text>
<Text component='p'>
If the process you wish to connect to does not have a Jolokia agent inside, please refer to the{' '}
<a href='http://jolokia.org/agent.html' target='_blank' rel='noreferrer'>
Jolokia documentation
</a>{' '}
for how to add a JVM, servlet or OSGi based agent inside it.
</Text>
<Text component='p'>
If you are using{' '}
<a href='https://developers.redhat.com/products/fuse/overview/' target='_blank' rel='noreferrer'>
Red Hat Fuse{' '}
</a>
or{' '}
<a href='http://activemq.apache.org/' target='_blank' rel='noreferrer'>
Apache ActiveMQ
</a>
, then a Jolokia agent is included by default (use context path of Jolokia agent, usually
<code>jolokia</code>). Or you can always just deploy hawtio inside the process (which includes the Jolokia agent,
use Jolokia servlet mapping inside hawtio context path, usually <code>hawtio/jolokia</code>).
</Text>
</ExpandableSection>
)

const ConnectContent: React.FunctionComponent = () => {
const { connections } = useContext(ConnectContext)
log.debug('Connections:', connections)

return (
<React.Fragment>
<ConnectToolbar />
<DataList id='connection-list' aria-label='connection list' isCompact>
{Object.entries(connections).map(([name, connection]) => (
<ConnectionItem key={name} name={name} connection={connection} />
))}
</DataList>
</React.Fragment>
)
}

const ConnectToolbar: React.FunctionComponent = () => {
const { connections } = useContext(ConnectContext)
const [isAddOpen, setIsAddOpen] = useState(false)
Expand Down Expand Up @@ -198,7 +208,7 @@ const ConnectionItem: React.FunctionComponent<ConnectionItemProps> = props => {
return
}

log.debug('Collecting:', connection)
log.debug('Connecting:', connection)
connectService.connect(connection)
}

Expand Down
9 changes: 8 additions & 1 deletion packages/hawtio/src/plugins/connect/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export async function isActive(): Promise<boolean> {
return false
}

return connectService.getCurrentConnectionName() === null
// The connect login path is exceptionally allowlisted to provide login form for
// remote Jolokia endpoints requiring authentication.
return connectService.getCurrentConnectionName() === null || isConnectLogin()
}

async function isProxyEnabled(): Promise<boolean> {
Expand All @@ -30,3 +32,8 @@ async function isProxyEnabled(): Promise<boolean> {
return true
}
}

function isConnectLogin(): boolean {
const url = new URL(window.location.href)
return url.pathname === connectService.getLoginPath()
}
75 changes: 75 additions & 0 deletions packages/hawtio/src/plugins/connect/login/ConnectLogin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { connectService } from '@hawtiosrc/plugins/shared'
import { Alert, Button, Form, FormAlert, FormGroup, Modal, TextInput } from '@patternfly/react-core'
import React, { useState } from 'react'

export const ConnectLogin: React.FunctionComponent = () => {
const [isOpen, setIsOpen] = useState(true)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [loginFailed, setLoginFailed] = useState(false)

const connectionName = connectService.getCurrentConnectionName()
if (!connectionName) {
return null
}

const handleLogin = () => {
const login = async () => {
const ok = await connectService.login(username, password)
if (ok) {
setLoginFailed(false)
// Redirect to the original URL
connectService.redirect()
} else {
setLoginFailed(true)
}
}
login()
}

const handleClose = () => {
setIsOpen(false)
}

const actions = [
<Button key='login' variant='primary' onClick={handleLogin}>
Log in
</Button>,
<Button key='cancel' variant='link' onClick={handleClose}>
Cancel
</Button>,
]

const title = `Log in to ${connectionName}`

return (
<Modal variant='small' title={title} isOpen={isOpen} onClose={handleClose} actions={actions}>
<Form id='connect-login-form' isHorizontal>
{loginFailed && (
<FormAlert>
<Alert variant='danger' title='Incorrect username or password' isInline />
</FormAlert>
)}
<FormGroup label='Username' isRequired fieldId='connect-login-form-username'>
<TextInput
isRequired
id='connect-login-form-username'
name='connect-login-form-username'
value={username}
onChange={value => setUsername(value)}
/>
</FormGroup>
<FormGroup label='Password' isRequired fieldId='connect-login-form-password'>
<TextInput
isRequired
id='connect-login-form-password'
name='connect-login-form-password'
type='password'
value={password}
onChange={value => setPassword(value)}
/>
</FormGroup>
</Form>
</Modal>
)
}
12 changes: 12 additions & 0 deletions packages/hawtio/src/plugins/shared/__mocks__/connect-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ class MockConnectService implements IConnectService {
// no-op
}

async login(username: string, password: string): Promise<boolean> {
return false
}

redirect() {
// no-op
}

getJolokiaUrl(connection: Connection): string {
return ''
}
Expand All @@ -50,6 +58,10 @@ class MockConnectService implements IConnectService {
return null
}

getLoginPath(): string {
return ''
}

export(connections: Connections) {
// no-op
}
Expand Down
Loading

0 comments on commit a8907d3

Please sign in to comment.