fix(chronograf): connecting to a source no longer takes you to host page

Co-authored-by: Andrew Watkins <andrew.watkinz@gmail.com>
Co-authored-by: Michael Desa <mjdesa@gmail.com>
pull/10616/head
Michael Desa 2018-08-03 13:08:22 -04:00 committed by Andrew Watkins
parent 30fc5282f6
commit 2af8a38777
14 changed files with 1 additions and 1299 deletions

View File

@ -1,294 +0,0 @@
import {proxy} from 'utils/queryUrlGenerator'
import replaceTemplate from 'src/tempVars/utils/replace'
import AJAX from 'utils/ajax'
import {
linksFromHosts,
updateActiveHostLink,
} from 'src/hosts/utils/hostsSwitcherLinks'
import _ from 'lodash'
export const getCpuAndLoadForHosts = (
proxyLink,
telegrafDB,
telegrafSystemInterval,
tempVars
) => {
const query = replaceTemplate(
`SELECT mean("usage_user") FROM \":db:\".\":rp:\".\"cpu\" WHERE "cpu" = 'cpu-total' AND time > now() - 10m GROUP BY host;
SELECT mean("load1") FROM \":db:\".\":rp:\".\"system\" WHERE time > now() - 10m GROUP BY host;
SELECT non_negative_derivative(mean(uptime)) AS deltaUptime FROM \":db:\".\":rp:\".\"system\" WHERE time > now() - ${telegrafSystemInterval} * 10 GROUP BY host, time(${telegrafSystemInterval}) fill(0);
SELECT mean("Percent_Processor_Time") FROM \":db:\".\":rp:\".\"win_cpu\" WHERE time > now() - 10m GROUP BY host;
SELECT mean("Processor_Queue_Length") FROM \":db:\".\":rp:\".\"win_system\" WHERE time > now() - 10s GROUP BY host;
SELECT non_negative_derivative(mean("System_Up_Time")) AS winDeltaUptime FROM \":db:\".\":rp:\".\"win_system\" WHERE time > now() - ${telegrafSystemInterval} * 10 GROUP BY host, time(${telegrafSystemInterval}) fill(0);
SHOW TAG VALUES WITH KEY = "host";`,
tempVars
)
return proxy({
source: proxyLink,
query,
db: telegrafDB,
tempVars,
}).then(resp => {
const hosts = {}
const precision = 100
const cpuSeries = _.get(resp, ['data', 'results', '0', 'series'], [])
const loadSeries = _.get(resp, ['data', 'results', '1', 'series'], [])
const uptimeSeries = _.get(resp, ['data', 'results', '2', 'series'], [])
const winCPUSeries = _.get(resp, ['data', 'results', '3', 'series'], [])
const winLoadSeries = _.get(resp, ['data', 'results', '4', 'series'], [])
const winUptimeSeries = _.get(resp, ['data', 'results', '5', 'series'], [])
const allHostsSeries = _.get(resp, ['data', 'results', '6', 'series'], [])
allHostsSeries.forEach(s => {
const hostnameIndex = s.columns.findIndex(col => col === 'value')
s.values.forEach(v => {
const hostname = v[hostnameIndex]
hosts[hostname] = {
name: hostname,
deltaUptime: -1,
cpu: 0.0,
load: 0.0,
}
})
})
cpuSeries.forEach(s => {
const meanIndex = s.columns.findIndex(col => col === 'mean')
hosts[s.tags.host] = {
name: s.tags.host,
cpu: Math.round(s.values[0][meanIndex] * precision) / precision,
}
})
loadSeries.forEach(s => {
const meanIndex = s.columns.findIndex(col => col === 'mean')
hosts[s.tags.host].load =
Math.round(s.values[0][meanIndex] * precision) / precision
})
uptimeSeries.forEach(s => {
const uptimeIndex = s.columns.findIndex(col => col === 'deltaUptime')
hosts[s.tags.host].deltaUptime =
s.values[s.values.length - 1][uptimeIndex]
})
winCPUSeries.forEach(s => {
const meanIndex = s.columns.findIndex(col => col === 'mean')
hosts[s.tags.host] = {
name: s.tags.host,
cpu: Math.round(s.values[0][meanIndex] * precision) / precision,
}
})
winLoadSeries.forEach(s => {
const meanIndex = s.columns.findIndex(col => col === 'mean')
hosts[s.tags.host].load =
Math.round(s.values[0][meanIndex] * precision) / precision
})
winUptimeSeries.forEach(s => {
const winUptimeIndex = s.columns.findIndex(
col => col === 'winDeltaUptime'
)
hosts[s.tags.host].winDeltaUptime =
s.values[s.values.length - 1][winUptimeIndex]
})
return hosts
})
}
async function getAllHosts(source) {
const {
telegraf,
links: {proxy: proxyLink},
} = source
try {
const resp = await proxy({
source: proxyLink,
query: 'show tag values with key = "host"',
db: telegraf,
})
const hosts = {}
const allHostsSeries = _.get(resp, ['data', 'results', '0', 'series'], [])
allHostsSeries.forEach(s => {
const hostnameIndex = s.columns.findIndex(col => col === 'value')
s.values.forEach(v => {
const hostname = v[hostnameIndex]
hosts[hostname] = {
name: hostname,
}
})
})
return hosts
} catch (error) {
console.error(error) // eslint-disable-line no-console
throw error
}
}
export const loadHostsLinks = async (
source,
{activeHost = {}, getHostNamesAJAX = getAllHosts} = {}
) => {
const hostNames = await getHostNamesAJAX(source)
const allLinks = linksFromHosts(hostNames, source)
return updateActiveHostLink(allLinks, activeHost)
}
export const getLayouts = () =>
AJAX({
method: 'GET',
resource: 'layouts',
})
export const getAppsForHost = (proxyLink, host, appLayouts, telegrafDB) => {
const measurements = appLayouts.map(m => `^${m.measurement}$`).join('|')
const measurementsToApps = _.zipObject(
appLayouts.map(m => m.measurement),
appLayouts.map(({app}) => app)
)
return proxy({
source: proxyLink,
query: `show series from /${measurements}/ where host = '${host}'`,
db: telegrafDB,
}).then(resp => {
const result = {apps: [], tags: {}}
const allSeries = _.get(resp, 'data.results.0.series.0.values', [])
allSeries.forEach(([series]) => {
const seriesObj = parseSeries(series)
const measurement = seriesObj.measurement
result.apps = _.uniq(result.apps.concat(measurementsToApps[measurement]))
_.assign(result.tags, seriesObj.tags)
})
return result
})
}
export const getAppsForHosts = (proxyLink, hosts, appLayouts, telegrafDB) => {
const measurements = appLayouts.map(m => `^${m.measurement}$`).join('|')
const measurementsToApps = _.zipObject(
appLayouts.map(m => m.measurement),
appLayouts.map(({app}) => app)
)
return proxy({
source: proxyLink,
query: `show series from /${measurements}/`,
db: telegrafDB,
}).then(resp => {
const newHosts = Object.assign({}, hosts)
const allSeries = _.get(
resp,
['data', 'results', '0', 'series', '0', 'values'],
[]
)
allSeries.forEach(([series]) => {
const seriesObj = parseSeries(series)
const measurement = seriesObj.measurement
const host = _.get(seriesObj, ['tags', 'host'], '')
if (!newHosts[host]) {
return
}
if (!newHosts[host].apps) {
newHosts[host].apps = []
}
if (!newHosts[host].tags) {
newHosts[host].tags = {}
}
newHosts[host].apps = _.uniq(
newHosts[host].apps.concat(measurementsToApps[measurement])
)
_.assign(newHosts[host].tags, seriesObj.tags)
})
return newHosts
})
}
export function getMeasurementsForHost(source, host) {
return proxy({
source: source.links.proxy,
query: `SHOW MEASUREMENTS WHERE "host" = '${host}'`,
db: source.telegraf,
}).then(({data}) => {
if (_isEmpty(data) || _hasError(data)) {
return []
}
const series = _.get(data, ['results', '0', 'series', '0'])
return series.values.map(measurement => {
return measurement[0]
})
})
}
function parseSeries(series) {
const ident = /\w+/
const tag = /,?([^=]+)=([^,]+)/
function parseMeasurement(s, obj) {
const match = ident.exec(s)
const measurement = match[0]
if (measurement) {
obj.measurement = measurement
}
return s.slice(match.index + measurement.length)
}
function parseTag(s, obj) {
const match = tag.exec(s)
if (match) {
const kv = match[0]
const key = match[1]
const value = match[2]
if (key) {
if (!obj.tags) {
obj.tags = {}
}
obj.tags[key] = value
}
return s.slice(match.index + kv.length)
}
return ''
}
let workStr = series.slice()
const out = {}
// Consume measurement
workStr = parseMeasurement(workStr, out)
// Consume tags
while (workStr.length > 0) {
workStr = parseTag(workStr, out)
}
return out
}
function _isEmpty(resp) {
return !resp.results[0].series
}
function _hasError(resp) {
return !!resp.results[0].error
}

View File

@ -1,83 +0,0 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import shallowCompare from 'react-addons-shallow-compare'
import {Link} from 'react-router'
import classnames from 'classnames'
import {HOSTS_TABLE} from 'src/hosts/constants/tableSizing'
import {ErrorHandling} from 'src/shared/decorators/errors'
@ErrorHandling
class HostRow extends Component {
constructor(props) {
super(props)
}
shouldComponentUpdate(nextProps) {
return shallowCompare(this, nextProps)
}
render() {
const {host, source} = this.props
const {name, cpu, load, apps = []} = host
const {colName, colStatus, colCPU, colLoad} = HOSTS_TABLE
return (
<div className="hosts-table--tr">
<div className="hosts-table--td" style={{width: colName}}>
<Link to={`/sources/${source.id}/hosts/${name}`}>{name}</Link>
</div>
<div className="hosts-table--td" style={{width: colStatus}}>
<div
className={classnames(
'table-dot',
Math.max(host.deltaUptime || 0, host.winDeltaUptime || 0) > 0
? 'dot-success'
: 'dot-critical'
)}
/>
</div>
<div style={{width: colCPU}} className="monotype hosts-table--td">
{isNaN(cpu) ? 'N/A' : `${cpu.toFixed(2)}%`}
</div>
<div style={{width: colLoad}} className="monotype hosts-table--td">
{isNaN(load) ? 'N/A' : `${load.toFixed(2)}`}
</div>
<div className="hosts-table--td">
{apps.map((app, index) => {
return (
<span key={app}>
<Link
style={{marginLeft: '2px'}}
to={{
pathname: `/sources/${source.id}/hosts/${name}`,
query: {app},
}}
>
{app}
</Link>
{index === apps.length - 1 ? null : ', '}
</span>
)
})}
</div>
</div>
)
}
}
HostRow.propTypes = {
source: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
}).isRequired,
host: PropTypes.shape({
name: PropTypes.string,
cpu: PropTypes.number,
load: PropTypes.number,
deltaUptime: PropTypes.number.required,
apps: PropTypes.arrayOf(PropTypes.string.isRequired),
}),
}
export default HostRow

View File

@ -1,191 +0,0 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import SearchBar from 'src/hosts/components/SearchBar'
import HostRow from 'src/hosts/components/HostRow'
import InfiniteScroll from 'shared/components/InfiniteScroll'
import {HOSTS_TABLE} from 'src/hosts/constants/tableSizing'
import {ErrorHandling} from 'src/shared/decorators/errors'
@ErrorHandling
class HostsTable extends Component {
constructor(props) {
super(props)
this.state = {
searchTerm: '',
sortDirection: null,
sortKey: null,
}
}
filter(allHosts, searchTerm) {
const filterText = searchTerm.toLowerCase()
return allHosts.filter(h => {
const apps = h.apps ? h.apps.join(', ') : ''
// search each tag for the presence of the search term
let tagResult = false
if (h.tags) {
tagResult = Object.keys(h.tags).reduce((acc, key) => {
return acc || h.tags[key].toLowerCase().includes(filterText)
}, false)
} else {
tagResult = false
}
return (
h.name.toLowerCase().includes(filterText) ||
apps.toLowerCase().includes(filterText) ||
tagResult
)
})
}
sort(hosts, key, direction) {
switch (direction) {
case 'asc':
return _.sortBy(hosts, e => e[key])
case 'desc':
return _.sortBy(hosts, e => e[key]).reverse()
default:
return hosts
}
}
updateSearchTerm = term => {
this.setState({searchTerm: term})
}
updateSort = key => () => {
// if we're using the key, reverse order; otherwise, set it with ascending
if (this.state.sortKey === key) {
const reverseDirection =
this.state.sortDirection === 'asc' ? 'desc' : 'asc'
this.setState({sortDirection: reverseDirection})
} else {
this.setState({sortKey: key, sortDirection: 'asc'})
}
}
sortableClasses = key => {
if (this.state.sortKey === key) {
if (this.state.sortDirection === 'asc') {
return 'hosts-table--th sortable-header sorting-ascending'
}
return 'hosts-table--th sortable-header sorting-descending'
}
return 'hosts-table--th sortable-header'
}
render() {
const {searchTerm, sortKey, sortDirection} = this.state
const {hosts, hostsLoading, hostsError, source} = this.props
const sortedHosts = this.sort(
this.filter(hosts, searchTerm),
sortKey,
sortDirection
)
const hostCount = sortedHosts.length
const {colName, colStatus, colCPU, colLoad} = HOSTS_TABLE
let hostsTitle
if (hostsLoading) {
hostsTitle = 'Loading Hosts...'
} else if (hostsError.length) {
hostsTitle = 'There was a problem loading hosts'
} else if (hostCount === 1) {
hostsTitle = `${hostCount} Host`
} else {
hostsTitle = `${hostCount} Hosts`
}
return (
<div className="panel">
<div className="panel-heading">
<h2 className="panel-title">{hostsTitle}</h2>
<SearchBar
placeholder="Filter by Host..."
onSearch={this.updateSearchTerm}
/>
</div>
<div className="panel-body">
{hostCount > 0 && !hostsError.length ? (
<div className="hosts-table">
<div className="hosts-table--thead">
<div className="hosts-table--tr">
<div
onClick={this.updateSort('name')}
className={this.sortableClasses('name')}
style={{width: colName}}
>
Host
<span className="icon caret-up" />
</div>
<div
onClick={this.updateSort('deltaUptime')}
className={this.sortableClasses('deltaUptime')}
style={{width: colStatus}}
>
Status
<span className="icon caret-up" />
</div>
<div
onClick={this.updateSort('cpu')}
className={this.sortableClasses('cpu')}
style={{width: colCPU}}
>
CPU
<span className="icon caret-up" />
</div>
<div
onClick={this.updateSort('load')}
className={this.sortableClasses('load')}
style={{width: colLoad}}
>
Load
<span className="icon caret-up" />
</div>
<div className="hosts-table--th">Apps</div>
</div>
</div>
<InfiniteScroll
items={sortedHosts.map(h => (
<HostRow key={h.name} host={h} source={source} />
))}
itemHeight={26}
className="hosts-table--tbody"
/>
</div>
) : (
<div className="generic-empty-state">
<h4 style={{margin: '90px 0'}}>No Hosts found</h4>
</div>
)}
</div>
</div>
)
}
}
const {arrayOf, bool, number, shape, string} = PropTypes
HostsTable.propTypes = {
hosts: arrayOf(
shape({
name: string,
cpu: number,
load: number,
apps: arrayOf(string.isRequired),
})
),
hostsLoading: bool,
hostsError: string,
source: shape({
id: string.isRequired,
name: string.isRequired,
}).isRequired,
}
export default HostsTable

View File

@ -1,6 +0,0 @@
export const HOSTS_TABLE = {
colName: '40%',
colStatus: '74px',
colCPU: '70px',
colLoad: '68px',
}

View File

@ -1,264 +0,0 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import classnames from 'classnames'
import LayoutRenderer from 'shared/components/LayoutRenderer'
import DashboardHeader from 'src/dashboards/components/DashboardHeader'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import ManualRefresh from 'src/shared/components/ManualRefresh'
import {generateForHosts} from 'src/utils/tempVars'
import {timeRanges} from 'shared/data/timeRanges'
import {
getLayouts,
getAppsForHost,
getMeasurementsForHost,
loadHostsLinks,
} from 'src/hosts/apis'
import {EMPTY_LINKS} from 'src/dashboards/constants/dashboardHeader'
import {setAutoRefresh, delayEnablePresentationMode} from 'shared/actions/app'
import {ErrorHandling} from 'src/shared/decorators/errors'
import AutoRefresh from 'src/utils/AutoRefresh'
@ErrorHandling
class HostPage extends Component {
constructor(props) {
super(props)
this.state = {
layouts: [],
hostLinks: EMPTY_LINKS,
timeRange: timeRanges.find(tr => tr.lower === 'now() - 1h'),
dygraphs: [],
}
}
async fetchHostsAndMeasurements(layouts) {
const {source, params} = this.props
const host = await getAppsForHost(
source.links.proxy,
params.hostID,
layouts,
source.telegraf
)
const measurements = await getMeasurementsForHost(source, params.hostID)
return {host, measurements}
}
async componentDidMount() {
const {
data: {layouts},
} = await getLayouts()
const {location, autoRefresh} = this.props
AutoRefresh.poll(autoRefresh)
// fetching layouts and mappings can be done at the same time
const {host, measurements} = await this.fetchHostsAndMeasurements(layouts)
const focusedApp = location.query.app
const filteredLayouts = layouts.filter(layout => {
if (focusedApp) {
return layout.app === focusedApp
}
return (
host.apps &&
host.apps.includes(layout.app) &&
measurements.includes(layout.measurement)
)
})
const hostLinks = await this.getHostLinks()
this.setState({layouts: filteredLayouts, hostLinks}) // eslint-disable-line react/no-did-mount-set-state
}
componentDidUpdate(prevProps) {
const {autoRefresh} = this.props
if (prevProps.autoRefresh !== autoRefresh) {
AutoRefresh.poll(autoRefresh)
}
}
componentWillUnmount() {
AutoRefresh.stopPolling()
}
handleChooseTimeRange = ({lower, upper}) => {
if (upper) {
this.setState({timeRange: {lower, upper}})
} else {
const timeRange = timeRanges.find(range => range.lower === lower)
this.setState({timeRange})
}
}
get layouts() {
const {timeRange, layouts} = this.state
if (layouts.length === 0) {
return ''
}
const {source, autoRefresh, manualRefresh} = this.props
const autoflowLayouts = layouts.filter(layout => !!layout.autoflow)
const cellWidth = 4
const cellHeight = 4
const pageWidth = 12
let cellCount = 0
const autoflowCells = autoflowLayouts.reduce((allCells, layout) => {
return allCells.concat(
layout.cells.map(cell => {
const x = (cellCount * cellWidth) % pageWidth
const y = Math.floor(cellCount * cellWidth / pageWidth) * cellHeight
cellCount += 1
return Object.assign(cell, {
w: cellWidth,
h: cellHeight,
x,
y,
})
})
)
}, [])
const staticLayouts = layouts.filter(layout => !layout.autoflow)
staticLayouts.unshift({cells: autoflowCells})
let translateY = 0
const layoutCells = staticLayouts.reduce((allCells, layout) => {
let maxY = 0
layout.cells.forEach(cell => {
cell.y += translateY
if (cell.y > translateY) {
maxY = cell.y
}
cell.queries.forEach(q => {
q.text = q.query
q.db = source.telegraf
q.rp = source.defaultRP
})
})
translateY = maxY
return allCells.concat(layout.cells)
}, [])
const tempVars = generateForHosts(source)
return (
<LayoutRenderer
source={source}
isEditable={false}
cells={layoutCells}
templates={tempVars}
timeRange={timeRange}
autoRefresh={autoRefresh}
manualRefresh={manualRefresh}
host={this.props.params.hostID}
/>
)
}
render() {
const {
autoRefresh,
onManualRefresh,
params: {hostID},
inPresentationMode,
handleChooseAutoRefresh,
handleClickPresentationButton,
} = this.props
const {timeRange, hostLinks} = this.state
return (
<div className="page">
<DashboardHeader
timeRange={timeRange}
activeDashboard={hostID}
autoRefresh={autoRefresh}
isHidden={inPresentationMode}
onManualRefresh={onManualRefresh}
handleChooseAutoRefresh={handleChooseAutoRefresh}
handleChooseTimeRange={this.handleChooseTimeRange}
handleClickPresentationButton={handleClickPresentationButton}
dashboardLinks={hostLinks}
/>
<FancyScrollbar
className={classnames({
'page-contents': true,
'presentation-mode': inPresentationMode,
})}
>
<div className="container-fluid full-width dashboard">
{this.layouts}
</div>
</FancyScrollbar>
</div>
)
}
getHostLinks = async () => {
const {
source,
params: {hostID},
} = this.props
const activeHost = {name: hostID}
const links = await loadHostsLinks(source, {activeHost})
return links
}
}
const {shape, string, bool, func, number} = PropTypes
HostPage.propTypes = {
source: shape({
links: shape({
proxy: string.isRequired,
}).isRequired,
telegraf: string.isRequired,
id: string.isRequired,
}),
params: shape({
hostID: string.isRequired,
}).isRequired,
location: shape({
query: shape({
app: string,
}),
}),
inPresentationMode: bool,
autoRefresh: number.isRequired,
manualRefresh: number.isRequired,
onManualRefresh: func.isRequired,
handleChooseAutoRefresh: func.isRequired,
handleClickPresentationButton: func,
}
const mstp = ({
app: {
ephemeral: {inPresentationMode},
persisted: {autoRefresh},
},
}) => ({
inPresentationMode,
autoRefresh,
})
const mdtp = {
handleChooseAutoRefresh: setAutoRefresh,
handleClickPresentationButton: delayEnablePresentationMode,
}
export default connect(mstp, mdtp)(ManualRefresh(HostPage))

View File

@ -1,207 +0,0 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import _ from 'lodash'
import HostsTable from 'src/hosts/components/HostsTable'
import AutoRefreshDropdown from 'shared/components/AutoRefreshDropdown'
import ManualRefresh from 'src/shared/components/ManualRefresh'
import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader'
import {getCpuAndLoadForHosts, getLayouts, getAppsForHosts} from '../apis'
import {getEnv} from 'src/shared/apis/env'
import {setAutoRefresh} from 'shared/actions/app'
import {notify as notifyAction} from 'shared/actions/notifications'
import {generateForHosts} from 'src/utils/tempVars'
import {
notifyUnableToGetHosts,
notifyUnableToGetApps,
} from 'shared/copy/notifications'
import {ErrorHandling} from 'src/shared/decorators/errors'
@ErrorHandling
export class HostsPage extends Component {
constructor(props) {
super(props)
this.state = {
hosts: {},
hostsLoading: true,
hostsError: '',
}
}
async fetchHostsData() {
const {source, links, notify} = this.props
const {telegrafSystemInterval} = await getEnv(links.environment)
const hostsError = notifyUnableToGetHosts().message
const tempVars = generateForHosts(source)
try {
const hosts = await getCpuAndLoadForHosts(
source.links.proxy,
source.telegraf,
telegrafSystemInterval,
tempVars
)
if (!hosts) {
throw new Error(hostsError)
}
const newHosts = await getAppsForHosts(
source.links.proxy,
hosts,
this.layouts,
source.telegraf
)
this.setState({
hosts: newHosts,
hostsError: '',
hostsLoading: false,
})
} catch (error) {
console.error(error)
notify(notifyUnableToGetHosts())
this.setState({
hostsError,
hostsLoading: false,
})
}
}
async componentDidMount() {
const {notify, autoRefresh} = this.props
this.componentIsMounted = true
this.setState({hostsLoading: true}) // Only print this once
const results = await getLayouts()
const data = _.get(results, 'data')
this.layouts = data && data.layouts
if (!this.layouts) {
const layoutError = notifyUnableToGetApps().message
notify(notifyUnableToGetApps())
this.setState({
hostsError: layoutError,
hostsLoading: false,
})
return
}
await this.fetchHostsData()
if (autoRefresh && this.componentIsMounted) {
this.intervalID = setInterval(() => this.fetchHostsData(), autoRefresh)
}
}
componentWillReceiveProps(nextProps) {
if (this.props.manualRefresh !== nextProps.manualRefresh) {
this.fetchHostsData()
}
if (this.props.autoRefresh !== nextProps.autoRefresh) {
clearInterval(this.intervalID)
if (nextProps.autoRefresh) {
this.intervalID = setInterval(
() => this.fetchHostsData(),
nextProps.autoRefresh
)
}
}
}
render() {
const {source} = this.props
const {hosts, hostsLoading, hostsError} = this.state
return (
<div className="page hosts-list-page">
<PageHeader
titleText="Host List"
optionsComponents={this.optionsComponents}
sourceIndicator={true}
/>
<div className="page-contents">
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<HostsTable
source={source}
hosts={_.values(hosts)}
hostsLoading={hostsLoading}
hostsError={hostsError}
/>
</div>
</div>
</div>
</div>
</div>
)
}
get optionsComponents() {
const {autoRefresh, onChooseAutoRefresh, onManualRefresh} = this.props
return (
<AutoRefreshDropdown
iconName="refresh"
selected={autoRefresh}
onChoose={onChooseAutoRefresh}
onManualRefresh={onManualRefresh}
/>
)
}
componentWillUnmount() {
this.componentIsMounted = false
clearInterval(this.intervalID)
this.intervalID = false
}
}
const {func, shape, string, number} = PropTypes
const mapStateToProps = state => {
const {
app: {
persisted: {autoRefresh},
},
links,
} = state
return {
links,
autoRefresh,
}
}
HostsPage.propTypes = {
source: shape({
id: string.isRequired,
name: string.isRequired,
type: string, // 'influx-enterprise'
links: shape({
proxy: string.isRequired,
}).isRequired,
telegraf: string.isRequired,
}),
links: shape({
environment: string.isRequired,
}),
autoRefresh: number.isRequired,
manualRefresh: number,
onChooseAutoRefresh: func.isRequired,
onManualRefresh: func.isRequired,
notify: func.isRequired,
}
HostsPage.defaultProps = {
manualRefresh: 0,
}
const mapDispatchToProps = dispatch => ({
onChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch),
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(
ManualRefresh(HostsPage)
)

View File

@ -1,3 +0,0 @@
import HostsPage from './containers/HostsPage'
import HostPage from './containers/HostPage'
export {HostsPage, HostPage}

View File

@ -1,32 +0,0 @@
import {Source} from 'src/types/sources'
import {HostNames, HostName} from 'src/types/hosts'
import {DashboardSwitcherLinks} from 'src/types/dashboards'
export const EMPTY_LINKS = {
links: [],
active: null,
}
export const linksFromHosts = (
hostNames: HostNames,
source: Source
): DashboardSwitcherLinks => {
const links = Object.values(hostNames).map(h => {
return {
key: h.name,
text: h.name,
to: `/sources/${source.id}/hosts/${h.name}`,
}
})
return {links, active: null}
}
export const updateActiveHostLink = (
hostLinks: DashboardSwitcherLinks,
host: HostName
): DashboardSwitcherLinks => {
const active = hostLinks.links.find(link => link.key === host.name)
return {...hostLinks, active}
}

View File

@ -17,7 +17,6 @@ import {getBasepath} from 'src/utils/basepath'
import App from 'src/App'
import CheckSources from 'src/CheckSources'
import {StatusPage} from 'src/status'
import {HostsPage, HostPage} from 'src/hosts'
import {DashboardsPage, DashboardPage} from 'src/dashboards'
import {LogsPage} from 'src/logs'
import {SourcePage, ManageSources} from 'src/sources'
@ -107,8 +106,6 @@ class Root extends PureComponent<{}, State> {
<Route path="/sources/:sourceID" component={App}>
<Route component={CheckSources}>
<Route path="status" component={StatusPage} />
<Route path="hosts" component={HostsPage} />
<Route path="hosts/:hostID" component={HostPage} />
<Route path="dashboards" component={DashboardsPage} />
<Route path="dashboards/:dashboardID" component={DashboardPage} />
<Route path="manage-sources" component={ManageSources} />

View File

@ -55,14 +55,6 @@ class SideNav extends PureComponent<Props> {
<span className="sidebar--icon icon cubo-uniform" />
</Link>
</div>
<NavBlock
highlightWhen={['hosts']}
icon="eye"
link={`${sourcePrefix}/hosts`}
location={location}
>
<NavHeader link={`${sourcePrefix}/hosts`} title="Host List" />
</NavBlock>
<NavBlock
highlightWhen={['delorean']}
icon="capacitor2"

View File

@ -65,7 +65,7 @@ class InfluxTableRow extends PureComponent<Props> {
return (
<Link
className="btn btn-default btn-xs source-table--connect"
to={`/sources/${source.id}/hosts`}
to={`/sources/${source.id}/manage-sources`}
>
Connect
</Link>

View File

@ -1,61 +0,0 @@
import {loadHostsLinks} from 'src/hosts/apis'
import {source} from 'test/resources'
import {HostNames} from 'src/types/hosts'
import {DashboardSwitcherLinks} from 'src/types/dashboards'
describe('hosts.apis.loadHostLinks', () => {
const socure = {...source, id: '897'}
const hostNames: HostNames = {
'zelda.local': {
name: 'zelda.local',
},
'gannon.local': {
name: 'gannon.local',
},
'korok.local': {
name: 'korok.local',
},
}
const hostNamesAJAX = async () => hostNames
const options = {
activeHost: {
name: 'korok.local',
},
getHostNamesAJAX: hostNamesAJAX,
}
it('can load the host links', async () => {
const hostLinks = await loadHostsLinks(socure, options)
const expectedLinks: DashboardSwitcherLinks = {
active: {
key: 'korok.local',
text: 'korok.local',
to: '/sources/897/hosts/korok.local',
},
links: [
{
key: 'zelda.local',
text: 'zelda.local',
to: '/sources/897/hosts/zelda.local',
},
{
key: 'gannon.local',
text: 'gannon.local',
to: '/sources/897/hosts/gannon.local',
},
{
key: 'korok.local',
text: 'korok.local',
to: '/sources/897/hosts/korok.local',
},
],
}
expect(hostLinks).toEqual(expectedLinks)
})
})

View File

@ -1,66 +0,0 @@
import React from 'react'
import {shallow} from 'enzyme'
import {HostsPage} from 'src/hosts/containers/HostsPage'
import HostsTable from 'src/hosts/components/HostsTable'
import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader'
import Title from 'src/reusable_ui/components/page_layout/PageHeaderTitle'
import {source} from 'test/resources'
jest.mock('src/hosts/apis', () => require('mocks/hosts/apis'))
jest.mock('src/shared/apis/env', () => require('mocks/shared/apis/env'))
import {getCpuAndLoadForHosts} from 'src/hosts/apis'
const setup = (override = {}) => {
const props = {
source,
links: {environment: ''},
autoRefresh: 0,
manualRefresh: 0,
onChooseAutoRefresh: () => {},
onManualRefresh: () => {},
notify: () => {},
...override,
}
const wrapper = shallow(<HostsPage {...props} />)
return {wrapper, props}
}
describe('Hosts.Containers.HostsPage', () => {
describe('rendering', () => {
it('renders all children components', () => {
const {wrapper} = setup()
const hostsTable = wrapper.find(HostsTable)
expect(hostsTable.exists()).toBe(true)
const pageTitle = wrapper
.find(PageHeader)
.dive()
.find(Title)
.dive()
.find('h1')
.first()
.text()
expect(pageTitle).toBe('Host List')
})
describe('hosts', () => {
it('renders hosts when response has hosts', done => {
const {wrapper} = setup()
process.nextTick(() => {
wrapper.update()
const hostsTable = wrapper.find(HostsTable)
expect(hostsTable.prop('hosts').length).toBe(1)
expect(getCpuAndLoadForHosts).toHaveBeenCalledTimes(2)
done()
})
})
})
})
})

View File

@ -1,80 +0,0 @@
import {
updateActiveHostLink,
linksFromHosts,
} from 'src/hosts/utils/hostsSwitcherLinks'
import {source} from 'test/resources'
import {HostNames} from 'src/types/hosts'
describe('hosts.utils.hostSwitcherLinks', () => {
describe('linksFromHosts', () => {
const socure = {...source, id: '897'}
const hostNames: HostNames = {
'zelda.local': {
name: 'zelda.local',
},
'gannon.local': {
name: 'gannon.local',
},
}
it('can build host links for a given source', () => {
const actualLinks = linksFromHosts(hostNames, socure)
const expectedLinks = {
links: [
{
key: 'zelda.local',
text: 'zelda.local',
to: '/sources/897/hosts/zelda.local',
},
{
key: 'gannon.local',
text: 'gannon.local',
to: '/sources/897/hosts/gannon.local',
},
],
active: null,
}
expect(actualLinks).toEqual(expectedLinks)
})
})
describe('updateActiveHostLink', () => {
const link1 = {
key: 'korok.local',
text: 'korok.local',
to: '/sources/897/hosts/korok.local',
}
const link2 = {
key: 'deku.local',
text: 'deku.local',
to: '/sources/897/hosts/deku.local',
}
const activeLink = {
key: 'robbie.local',
text: 'robbie.local',
to: '/sources/897/hosts/robbie.local',
}
const activeHostName = {
name: 'robbie.local',
}
const links = [link1, activeLink, link2]
it('can set the active host link', () => {
const loadedLinks = {
links,
active: null,
}
const actualLinks = updateActiveHostLink(loadedLinks, activeHostName)
const expectedLinks = {links, active: activeLink}
expect(actualLinks).toEqual(expectedLinks)
})
})
})