feat(ui): add independent Flux Tasks page
parent
6d9e333b32
commit
cc34b79390
|
@ -30,6 +30,7 @@ import {LogsPage} from 'src/logs'
|
|||
import AlertsApp from 'src/alerts'
|
||||
import {
|
||||
FluxTaskPage,
|
||||
FluxTasksPage,
|
||||
KapacitorPage,
|
||||
KapacitorRulePage,
|
||||
KapacitorRulesPage,
|
||||
|
@ -188,6 +189,7 @@ class Root extends PureComponent<Record<string, never>, State> {
|
|||
/>
|
||||
<Route path="alerts" component={AlertsApp} />
|
||||
<Route path="alert-rules" component={KapacitorRulesPage} />
|
||||
<Route path="flux-tasks" component={FluxTasksPage} />
|
||||
<Route
|
||||
path="kapacitors/:kid/alert-rules/:ruleID" // ruleID can be "new"
|
||||
component={KapacitorRulePage}
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
import React, {useEffect, useState} from 'react'
|
||||
import {FluxTask, Kapacitor, Source} from 'src/types'
|
||||
import KapacitorScopedPage from './KapacitorScopedPage'
|
||||
import {useDispatch} from 'react-redux'
|
||||
import {deleteFluxTask, getFluxTasks, updateFluxTaskStatus} from '../apis'
|
||||
import errorMessage from '../utils/errorMessage'
|
||||
import PageSpinner from 'src/shared/components/PageSpinner'
|
||||
import FluxTasksTable from '../components/FluxTasksTable'
|
||||
import {notify} from 'src/shared/actions/notifications'
|
||||
import {
|
||||
notifyAlertRuleDeleted,
|
||||
notifyAlertRuleDeleteFailed,
|
||||
notifyFluxTaskStatusUpdated,
|
||||
notifyFluxTaskStatusUpdateFailed,
|
||||
} from 'src/shared/copy/notifications'
|
||||
import useDebounce from '../../utils/useDebounce'
|
||||
|
||||
const Contents = ({
|
||||
kapacitor,
|
||||
source,
|
||||
}: {
|
||||
kapacitor: Kapacitor
|
||||
source: Source
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [nameFilter, setNameFilter] = useState('')
|
||||
const [reloadRequired, setReloadRequired] = useState(0)
|
||||
const [error, setError] = useState(undefined)
|
||||
const [list, setList] = useState<FluxTask[] | null>(null)
|
||||
const dispatch = useDispatch()
|
||||
const filter = useDebounce(nameFilter)
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
let data = (await getFluxTasks(kapacitor)) as FluxTask[]
|
||||
if (data && filter) {
|
||||
data = data.filter(x => x.name.includes(filter))
|
||||
}
|
||||
setList(data)
|
||||
} catch (e) {
|
||||
if (e.status === 404) {
|
||||
setList(null)
|
||||
} else {
|
||||
console.error(e)
|
||||
setError(
|
||||
new Error(
|
||||
e?.data?.message
|
||||
? e.data.message
|
||||
: `Cannot load flux task: ${errorMessage(e)}`
|
||||
)
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
fetchData()
|
||||
}, [kapacitor, reloadRequired, filter])
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="panel panel-solid">
|
||||
<div className="panel-body">
|
||||
<p className="unexpected_error">{error.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (list === null) {
|
||||
return (
|
||||
<div className="panel panel-solid">
|
||||
<div className="panel-body">
|
||||
<p className="unexpected_error">
|
||||
No flux tasks are available. Kapacitor 1.6+ is required with Flux
|
||||
tasks enabled.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="panel">
|
||||
<div className="panel-heading">
|
||||
<div className="search-widget" style={{flexGrow: 1}}>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control input-sm"
|
||||
placeholder="Search name"
|
||||
value={nameFilter}
|
||||
onChange={e => {
|
||||
setNameFilter(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<span className="icon search" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
{loading ? (
|
||||
<PageSpinner />
|
||||
) : (
|
||||
<FluxTasksTable
|
||||
kapacitorLink={`/sources/${source.id}/kapacitors/${kapacitor.id}`}
|
||||
tasks={list}
|
||||
onDelete={(task: FluxTask) => {
|
||||
deleteFluxTask(kapacitor, task)
|
||||
.then(() => {
|
||||
setReloadRequired(reloadRequired + 1)
|
||||
dispatch(notify(notifyAlertRuleDeleted(task.name)))
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(notify(notifyAlertRuleDeleteFailed(task.name)))
|
||||
})
|
||||
}}
|
||||
onChangeTaskStatus={(task: FluxTask) => {
|
||||
const status = task.status === 'active' ? 'inactive' : 'active'
|
||||
updateFluxTaskStatus(kapacitor, task, status)
|
||||
.then(() => {
|
||||
setList(
|
||||
list.map(x => (x.id === task.id ? {...task, status} : x))
|
||||
)
|
||||
dispatch(
|
||||
notify(notifyFluxTaskStatusUpdated(task.name, status))
|
||||
)
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(
|
||||
notify(notifyFluxTaskStatusUpdateFailed(task.name, status))
|
||||
)
|
||||
return false
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const FluxTasksPage = ({source: src}: {source: Source}) => {
|
||||
return (
|
||||
<KapacitorScopedPage source={src} title="Flux Tasks">
|
||||
{(kapacitor: Kapacitor, source: Source) => (
|
||||
<Contents kapacitor={kapacitor} source={source} />
|
||||
)}
|
||||
</KapacitorScopedPage>
|
||||
)
|
||||
}
|
||||
|
||||
export default FluxTasksPage
|
|
@ -0,0 +1,131 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
// APIs
|
||||
import {getKapacitors, pingKapacitor} from 'src/shared/apis'
|
||||
|
||||
// Utils
|
||||
import {notifyKapacitorConnectionFailed} from 'src/shared/copy/notifications'
|
||||
|
||||
// Actions
|
||||
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||
import {setActiveKapacitorAsync} from 'src/shared/actions/sources'
|
||||
|
||||
// Components
|
||||
import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
|
||||
import {Page, Spinner} from 'src/reusable_ui'
|
||||
import Dropdown from 'src/reusable_ui/components/dropdowns/Dropdown'
|
||||
|
||||
// Types
|
||||
import {Source, Kapacitor, RemoteDataState} from 'src/types'
|
||||
|
||||
// Decorators
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import NoKapacitorError from 'src/shared/components/NoKapacitorError'
|
||||
|
||||
interface Props {
|
||||
// connected props
|
||||
notify: typeof mdtp.notify
|
||||
setActiveKapacitor: typeof mdtp.setActiveKapacitor
|
||||
|
||||
// owen props
|
||||
title: string
|
||||
source: Source
|
||||
tooltip?: string
|
||||
children: (kapacitor: Kapacitor, source: Source) => JSX.Element
|
||||
}
|
||||
|
||||
interface State {
|
||||
loading: RemoteDataState
|
||||
kapacitors?: Kapacitor[]
|
||||
kapacitor?: Kapacitor
|
||||
error?: Error
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
export class KapacitorScopedPage extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
loading: RemoteDataState.NotStarted,
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const {source} = this.props
|
||||
this.setState({loading: RemoteDataState.Loading})
|
||||
getKapacitors(source).then(kapacitors => {
|
||||
const kapacitor =
|
||||
kapacitors && kapacitors.length
|
||||
? kapacitors.find(x => x.active) || kapacitors[0]
|
||||
: undefined
|
||||
|
||||
this.setState({kapacitors, kapacitor, loading: RemoteDataState.Done})
|
||||
})
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {tooltip, title, source, children} = this.props
|
||||
const {loading, kapacitor, kapacitors} = this.state
|
||||
return (
|
||||
<Page className={kapacitor ? '' : 'empty-tasks-page'}>
|
||||
<Page.Header>
|
||||
<Page.Header.Left>
|
||||
<Page.Title title={kapacitor ? `${title} on` : title} />
|
||||
{kapacitor ? (
|
||||
<Dropdown
|
||||
customClass="kapacitor-switcher"
|
||||
onChange={this.handleSetActiveKapacitor}
|
||||
widthPixels={330}
|
||||
selectedID={kapacitor.id}
|
||||
>
|
||||
{kapacitors.map(k => (
|
||||
<Dropdown.Item key={k.id} id={k.id} value={k.id}>
|
||||
{`${k.name} @ ${k.url}`}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
</Dropdown>
|
||||
) : undefined}
|
||||
</Page.Header.Left>
|
||||
<Page.Header.Right showSourceIndicator={true}>
|
||||
{tooltip ? (
|
||||
<QuestionMarkTooltip
|
||||
tipID="manage-tasks--tooltip"
|
||||
tipContent={tooltip}
|
||||
/>
|
||||
) : undefined}
|
||||
</Page.Header.Right>
|
||||
</Page.Header>
|
||||
<Page.Contents>
|
||||
<Spinner loading={loading}>
|
||||
{kapacitor ? (
|
||||
children(kapacitor, source)
|
||||
) : (
|
||||
<NoKapacitorError source={source} />
|
||||
)}
|
||||
</Spinner>
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
private handleSetActiveKapacitor = async (
|
||||
kapacitorID: string
|
||||
): Promise<void> => {
|
||||
const {setActiveKapacitor} = this.props
|
||||
const {kapacitors} = this.state
|
||||
const toKapacitor = kapacitors.find(k => k.id === kapacitorID)
|
||||
setActiveKapacitor(toKapacitor)
|
||||
pingKapacitor(toKapacitor).catch(() => {
|
||||
this.props.notify(notifyKapacitorConnectionFailed())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const mdtp = {
|
||||
notify: notifyAction,
|
||||
setActiveKapacitor: setActiveKapacitorAsync,
|
||||
}
|
||||
|
||||
export default connect(null, mdtp)(KapacitorScopedPage)
|
|
@ -3,6 +3,7 @@ import KapacitorRulePage from './containers/KapacitorRulePage'
|
|||
import KapacitorRulesPage from './containers/KapacitorRulesPage'
|
||||
import TickscriptPage from './containers/TickscriptPage'
|
||||
import FluxTaskPage from './containers/FluxTaskPage'
|
||||
import FluxTasksPage from './containers/FluxTasksPage'
|
||||
|
||||
export {
|
||||
KapacitorPage,
|
||||
|
@ -10,4 +11,5 @@ export {
|
|||
KapacitorRulesPage,
|
||||
TickscriptPage,
|
||||
FluxTaskPage,
|
||||
FluxTasksPage,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue