WIP
parent
b6517263ec
commit
8b9afc1702
|
@ -0,0 +1,126 @@
|
||||||
|
import React, {PropTypes} from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const AlertsTable = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
hosts: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
cpu: PropTypes.number,
|
||||||
|
load: PropTypes.number,
|
||||||
|
})),
|
||||||
|
source: PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
searchTerm: '',
|
||||||
|
filteredHosts: this.props.hosts,
|
||||||
|
sortDirection: null,
|
||||||
|
sortKey: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps(newProps) {
|
||||||
|
this.filterHosts(newProps.hosts, this.state.searchTerm);
|
||||||
|
},
|
||||||
|
|
||||||
|
filterHosts(allHosts, searchTerm) {
|
||||||
|
const hosts = allHosts.filter((h) => h.name.search(searchTerm) !== -1);
|
||||||
|
this.setState({searchTerm, filteredHosts: hosts});
|
||||||
|
},
|
||||||
|
|
||||||
|
changeSort(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'});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const hosts = this.sort(this.state.filteredHosts, this.state.sortKey, this.state.sortDirection);
|
||||||
|
const {source} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="panel panel-minimal">
|
||||||
|
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||||
|
<h2 className="panel-title">{this.props.hosts.length} Hosts</h2>
|
||||||
|
<SearchBar onSearch={_.wrap(this.props.hosts, this.filterHosts)} />
|
||||||
|
</div>
|
||||||
|
<div className="panel-body">
|
||||||
|
<table className="table v-center">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th onClick={() => this.changeSort('name')} className="sortable-header">Hostname</th>
|
||||||
|
<th className="text-center">Status</th>
|
||||||
|
<th onClick={() => this.changeSort('cpu')} className="sortable-header">CPU</th>
|
||||||
|
<th onClick={() => this.changeSort('load')} className="sortable-header">Load</th>
|
||||||
|
<th>Apps</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
hosts.map(({name, cpu, load}) => {
|
||||||
|
return (
|
||||||
|
<tr key={name}>
|
||||||
|
<td className="monotype"><a href={`/sources/${source.id}/hosts/${name}`}>{name}</a></td>
|
||||||
|
<td className="text-center"><div className="table-dot dot-success"></div></td>
|
||||||
|
<td className="monotype">{`${cpu.toFixed(2)}%`}</td>
|
||||||
|
<td className="monotype">{`${load.toFixed(2)}`}</td>
|
||||||
|
<td className="monotype">influxdb, ntp, system</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const SearchBar = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
onSearch: PropTypes.func.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChange() {
|
||||||
|
this.props.onSearch(this.refs.searchInput.value);
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="users__search-widget input-group">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
placeholder="Filter Hosts"
|
||||||
|
ref="searchInput"
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
<div className="input-group-addon">
|
||||||
|
<span className="icon search" aria-hidden="true"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AlertsTable;
|
|
@ -0,0 +1,52 @@
|
||||||
|
import React, {PropTypes} from 'react';
|
||||||
|
import AlertsTable from '../components/AlertsTable';
|
||||||
|
|
||||||
|
const AlertsApp = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
source: PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.string, // 'influx-enterprise'
|
||||||
|
links: PropTypes.shape({
|
||||||
|
proxy: PropTypes.string.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
addFlashMessage: PropTypes.func.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
alerts: [],
|
||||||
|
hosts: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
// I stole this from the Hosts page.
|
||||||
|
// Perhaps we should create an abstraction?
|
||||||
|
<div className="hosts hosts-page">
|
||||||
|
<div className="enterprise-header">
|
||||||
|
<div className="enterprise-header__container">
|
||||||
|
<div className="enterprise-header__left">
|
||||||
|
<h1>
|
||||||
|
Alerts
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="container-fluid">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-12">
|
||||||
|
<AlertsTable source={this.props.source} hosts={this.state.hosts} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AlertsApp;
|
|
@ -0,0 +1,2 @@
|
||||||
|
import AlertsApp from './containers/AlertsApp';
|
||||||
|
export default AlertsApp;
|
|
@ -4,6 +4,7 @@ import {Provider} from 'react-redux';
|
||||||
import {Router, Route, browserHistory} from 'react-router';
|
import {Router, Route, browserHistory} from 'react-router';
|
||||||
|
|
||||||
import App from 'src/App';
|
import App from 'src/App';
|
||||||
|
import AlertsApp from 'src/alerts';
|
||||||
import CheckDataNodes from 'src/CheckDataNodes';
|
import CheckDataNodes from 'src/CheckDataNodes';
|
||||||
import {HostsPage, HostPage} from 'src/hosts';
|
import {HostsPage, HostPage} from 'src/hosts';
|
||||||
import {KapacitorPage, KapacitorTasksPage} from 'src/kapacitor';
|
import {KapacitorPage, KapacitorTasksPage} from 'src/kapacitor';
|
||||||
|
@ -115,6 +116,7 @@ const Root = React.createClass({
|
||||||
<Route path="hosts/:hostID" component={HostPage} />
|
<Route path="hosts/:hostID" component={HostPage} />
|
||||||
<Route path="kapacitor-config" component={KapacitorPage} />
|
<Route path="kapacitor-config" component={KapacitorPage} />
|
||||||
<Route path="kapacitor-tasks" component={KapacitorTasksPage} />
|
<Route path="kapacitor-tasks" component={KapacitorTasksPage} />
|
||||||
|
<Route path="alerts" component={AlertsApp} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="tasks" component={TasksPage} />
|
<Route path="tasks" component={TasksPage} />
|
||||||
<Route path="*" component={NotFound} />
|
<Route path="*" component={NotFound} />
|
||||||
|
|
Loading…
Reference in New Issue