Merge pull request #202 from influxdata/feature/tr-remove-overview
Remove Overview pagepull/10616/head
commit
2a44bc6807
|
@ -1,44 +0,0 @@
|
||||||
import NodeTable from 'src/overview/components/NodeTable';
|
|
||||||
import NodeTableRow from 'src/overview/components/NodeTableRow';
|
|
||||||
import React from 'react';
|
|
||||||
import TestUtils, {renderIntoDocument, scryRenderedComponentsWithType} from 'react-addons-test-utils';
|
|
||||||
import {findDOMNode} from 'react-dom';
|
|
||||||
|
|
||||||
describe('Overview.Components.NodeTable', () => {
|
|
||||||
it('renders a NodeTableRow for each node (meta + data)', () => {
|
|
||||||
const cluster = {
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"tcpAddr": "localhost:8088",
|
|
||||||
"httpAddr": "localhost:8086",
|
|
||||||
"id": 2,
|
|
||||||
"status": "joined"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tcpAddr": "localhost:8188",
|
|
||||||
"httpAddr": "localhost:8186",
|
|
||||||
"id": 6,
|
|
||||||
"status": "joined"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"meta": [
|
|
||||||
{
|
|
||||||
"addr": "localhost:8091"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id": "a cluster id"
|
|
||||||
};
|
|
||||||
|
|
||||||
const component = renderIntoDocument(
|
|
||||||
<NodeTable
|
|
||||||
clusterID='a cluster id'
|
|
||||||
cluster={cluster}
|
|
||||||
dataNodes={['localhost:8088', 'localhost:8188']}
|
|
||||||
refreshIntervalMs={2000}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const nodeRows = scryRenderedComponentsWithType(component, NodeTableRow);
|
|
||||||
|
|
||||||
expect(nodeRows.length).to.equal(3);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,109 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import TestUtils, {renderIntoDocument, scryRenderedComponentsWithType} from 'react-addons-test-utils';
|
|
||||||
import {findDOMNode} from 'react-dom';
|
|
||||||
import {mount, shallow} from 'enzyme';
|
|
||||||
import sinon from 'sinon';
|
|
||||||
import * as api from 'src/shared/apis';
|
|
||||||
|
|
||||||
import {OverviewPage} from 'src/overview/containers/OverviewPage';
|
|
||||||
import ClusterStatsPanel from 'src/overview/components/ClusterStatsPanel';
|
|
||||||
import MiscStatsPanel from 'src/overview/components/MiscStatsPanel';
|
|
||||||
import NodeTable from 'src/overview/components/NodeTable';
|
|
||||||
|
|
||||||
const clusterID = '1000';
|
|
||||||
const showClustersResponse = {
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
tcpAddr: 'localhost:8088',
|
|
||||||
httpAddr: 'localhost:8086',
|
|
||||||
id: 1,
|
|
||||||
status: 'joined'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tcpAddr: 'localhost:8188',
|
|
||||||
httpAddr: 'localhost:8186',
|
|
||||||
id: 2,
|
|
||||||
status: 'joined'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
meta: [
|
|
||||||
{
|
|
||||||
addr: 'localhost:8091'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
function setup(customProps = {}) {
|
|
||||||
const defaultProps = {
|
|
||||||
dataNodes: ['localhost:8086'],
|
|
||||||
params: {clusterID},
|
|
||||||
addFlashMessage: function() {},
|
|
||||||
};
|
|
||||||
const props = Object.assign({}, defaultProps, customProps);
|
|
||||||
return mount(<OverviewPage {...props} />);
|
|
||||||
}
|
|
||||||
|
|
||||||
xdescribe('Overview.Containers.OverviewPage', function() {
|
|
||||||
it('renders a spinner initially', function() {
|
|
||||||
const wrapper = shallow(<OverviewPage dataNodes={['localhost:8086']} params={{clusterID}} addFlashMessage={() => {}} />);
|
|
||||||
|
|
||||||
expect(wrapper.contains(<div className="page-spinner" />)).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('after being mounted', function() {
|
|
||||||
let stub;
|
|
||||||
beforeEach(function() {
|
|
||||||
stub = sinon.stub(api, 'showCluster').returns(Promise.resolve({
|
|
||||||
data: showClustersResponse,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {
|
|
||||||
stub.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fetches cluster information after being mounted', function() {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
expect(api.showCluster.calledWith(clusterID)).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('hides the spinner', function(done) {
|
|
||||||
const wrapper = setup();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
expect(wrapper.contains(<div className="page-spinner" />)).to.be.false;
|
|
||||||
done();
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fetches cluster information after being mounted', function() {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
expect(api.showCluster.calledWith(clusterID)).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders the correct panels', function(done) {
|
|
||||||
const wrapper = setup();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
expect(wrapper.find(ClusterStatsPanel)).to.have.length(1);
|
|
||||||
expect(wrapper.find(MiscStatsPanel)).to.have.length(1);
|
|
||||||
expect(wrapper.find(NodeTable)).to.have.length(1);
|
|
||||||
done();
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('passes the correct props to NodeTable', function(done) {
|
|
||||||
const wrapper = setup();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
const table = wrapper.find(NodeTable);
|
|
||||||
expect(table.props().cluster).to.eql(showClustersResponse);
|
|
||||||
expect(table.props().dataNodes).to.eql(['localhost:8086']);
|
|
||||||
expect(table.props().clusterID).to.equal(clusterID);
|
|
||||||
done();
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -6,7 +6,6 @@ import {Router, Route, browserHistory} from 'react-router';
|
||||||
import App from 'src/App';
|
import App from 'src/App';
|
||||||
import CheckDataNodes from 'src/CheckDataNodes';
|
import CheckDataNodes from 'src/CheckDataNodes';
|
||||||
import {HostsPage, HostPage} from 'src/hosts';
|
import {HostsPage, HostPage} from 'src/hosts';
|
||||||
import OverviewPage from 'src/overview';
|
|
||||||
import QueriesPage from 'src/queries';
|
import QueriesPage from 'src/queries';
|
||||||
import TasksPage from 'src/tasks';
|
import TasksPage from 'src/tasks';
|
||||||
import RetentionPoliciesPage from 'src/retention_policies';
|
import RetentionPoliciesPage from 'src/retention_policies';
|
||||||
|
@ -122,7 +121,6 @@ const Root = React.createClass({
|
||||||
<Route path="/sources" component={SelectSourcePage} />
|
<Route path="/sources" component={SelectSourcePage} />
|
||||||
<Route path="/sources/:sourceID" component={App}>
|
<Route path="/sources/:sourceID" component={App}>
|
||||||
<Route component={CheckDataNodes}>
|
<Route component={CheckDataNodes}>
|
||||||
<Route path="overview" component={OverviewPage} />
|
|
||||||
<Route path="queries" component={QueriesPage} />
|
<Route path="queries" component={QueriesPage} />
|
||||||
<Route path="accounts" component={ClusterAccountsPage} />
|
<Route path="accounts" component={ClusterAccountsPage} />
|
||||||
<Route path="accounts/:accountID" component={ClusterAccountPage} />
|
<Route path="accounts/:accountID" component={ClusterAccountPage} />
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
import React, {PropTypes} from 'react';
|
|
||||||
import MiniGraph from 'shared/components/MiniGraph';
|
|
||||||
import AutoRefresh from 'shared/components/AutoRefresh';
|
|
||||||
const RefreshingMiniGraph = AutoRefresh(MiniGraph);
|
|
||||||
import {OVERVIEW_TIME_RANGE, OVERVIEW_INTERVAL} from '../constants';
|
|
||||||
|
|
||||||
const ClusterStatsPanel = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
dataNodes: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
|
|
||||||
clusterID: PropTypes.string.isRequired,
|
|
||||||
refreshIntervalMs: PropTypes.number.isRequired,
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
const {dataNodes, refreshIntervalMs} = this.props;
|
|
||||||
|
|
||||||
// For active writes/queries, we GROUP BY nodeID because we want to grab all of the
|
|
||||||
// values for each node and sum the results.
|
|
||||||
const queries = [
|
|
||||||
{
|
|
||||||
queryDescription: "Active Queries",
|
|
||||||
host: dataNodes,
|
|
||||||
database: '_internal',
|
|
||||||
text: `SELECT NON_NEGATIVE_DERIVATIVE(MAX(queryReq)) FROM httpd WHERE time > now() - ${OVERVIEW_TIME_RANGE} AND clusterID='${this.props.clusterID}' GROUP by nodeID, time(${OVERVIEW_INTERVAL})`,
|
|
||||||
options: {combineSeries: true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
queryDescription: "Avg Query Latency (ms)",
|
|
||||||
host: dataNodes,
|
|
||||||
database: '_internal',
|
|
||||||
text: `SELECT mean(queryDurationNs)/100000000000 FROM queryExecutor WHERE time > now() - ${OVERVIEW_TIME_RANGE} AND clusterID='${this.props.clusterID}' GROUP BY time(${OVERVIEW_INTERVAL})`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
queryDescription: "Active Writes",
|
|
||||||
host: dataNodes,
|
|
||||||
database: '_internal',
|
|
||||||
text: `SELECT NON_NEGATIVE_DERIVATIVE(MAX(req)) FROM "write" WHERE time > now() - ${OVERVIEW_TIME_RANGE} AND clusterID='${this.props.clusterID}' GROUP BY nodeID, time(${OVERVIEW_INTERVAL})`,
|
|
||||||
options: {combineSeries: true},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="col-lg-6">
|
|
||||||
<div className="panel panel-minimal">
|
|
||||||
<div className="panel-heading">
|
|
||||||
<h2 className="panel-title">Cluster Speed</h2>
|
|
||||||
</div>
|
|
||||||
<div className="panel-body" style={{height: '206px'}}>
|
|
||||||
{
|
|
||||||
queries.map(({options, host, database, text, queryDescription}, i) => {
|
|
||||||
return (
|
|
||||||
<RefreshingMiniGraph clusterID={this.props.clusterID} options={options} key={i} queries={[{host, database, text}]} autoRefresh={refreshIntervalMs} queryDescription={queryDescription}>
|
|
||||||
<p key={i} className="cluster-stat-empty"><span className="icon cubo"></span> Waiting for stats...</p>
|
|
||||||
</RefreshingMiniGraph>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ClusterStatsPanel;
|
|
|
@ -1,81 +0,0 @@
|
||||||
import React, {PropTypes} from 'react';
|
|
||||||
import {formatBytes} from 'utils/formatting';
|
|
||||||
import {diskBytesFromShard} from 'shared/parsing/diskBytes';
|
|
||||||
import {clusterDiskUsage} from 'shared/apis/stats';
|
|
||||||
import MiniGraph from 'shared/components/MiniGraph';
|
|
||||||
import AutoRefresh from 'shared/components/AutoRefresh';
|
|
||||||
const RefreshingMiniGraph = AutoRefresh(MiniGraph);
|
|
||||||
import {OVERVIEW_TIME_RANGE, OVERVIEW_INTERVAL} from '../constants';
|
|
||||||
|
|
||||||
const MiscStatsPanel = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
dataNodes: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
|
|
||||||
clusterID: PropTypes.string.isRequired,
|
|
||||||
refreshIntervalMs: PropTypes.number.isRequired,
|
|
||||||
},
|
|
||||||
getInitialState() {
|
|
||||||
return {
|
|
||||||
diskUsed: 0,
|
|
||||||
influxVersion: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
componentDidMount() {
|
|
||||||
clusterDiskUsage(this.props.dataNodes, this.props.clusterID).then((res) => {
|
|
||||||
this.setState({
|
|
||||||
diskUsed: diskBytesFromShard(res.data).bytes,
|
|
||||||
influxVersion: res.headers['x-influxdb-version'],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
const {influxVersion, diskUsed} = this.state;
|
|
||||||
const {dataNodes, refreshIntervalMs} = this.props;
|
|
||||||
const queries = [
|
|
||||||
{
|
|
||||||
queryDescription: 'Bytes Allocated',
|
|
||||||
host: dataNodes,
|
|
||||||
database: '_internal',
|
|
||||||
text: `select max(Alloc) AS bytes_allocated from runtime where time > now() - ${OVERVIEW_TIME_RANGE} AND clusterID='${this.props.clusterID}' group by time(${OVERVIEW_INTERVAL})`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
queryDescription: 'Heap Bytes',
|
|
||||||
host: dataNodes,
|
|
||||||
database: '_internal',
|
|
||||||
text: `select max(HeapInUse) AS heap_bytes from runtime where time > now() - ${OVERVIEW_TIME_RANGE} AND clusterID='${this.props.clusterID}' group by time(${OVERVIEW_INTERVAL})`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="col-lg-6">
|
|
||||||
<div className="panel panel-minimal">
|
|
||||||
<div className="panel-heading">
|
|
||||||
<h2 className="panel-title">Misc Stats</h2>
|
|
||||||
</div>
|
|
||||||
<div className="panel-body" style={{height: '206px'}}>
|
|
||||||
<div className="cluster-stat-2x">
|
|
||||||
<div className="influx-version">
|
|
||||||
<span className="cluster-stat-2x--label">Version:</span><span className="cluster-stat-2x--number">{influxVersion}</span>
|
|
||||||
</div>
|
|
||||||
<div className="disk-util">
|
|
||||||
<span className="cluster-stat-2x--label">Disk Use:</span><span className="cluster-stat-2x--number">{formatBytes(diskUsed)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="top-stuff">
|
|
||||||
{
|
|
||||||
queries.map(({host, database, text, queryDescription}, i) => {
|
|
||||||
return (
|
|
||||||
<RefreshingMiniGraph key={i} clusterID={this.props.clusterID} queries={[{host, database, text}]} autoRefresh={refreshIntervalMs} queryDescription={queryDescription}>
|
|
||||||
<p className="cluster-stat-empty"><span className="icon cubo"></span> Waiting for stats...</p>
|
|
||||||
</RefreshingMiniGraph>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default MiscStatsPanel;
|
|
|
@ -1,80 +0,0 @@
|
||||||
import React, {PropTypes} from 'react';
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import NodeTableRow from './NodeTableRow';
|
|
||||||
|
|
||||||
const NodeTable = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
refreshIntervalMs: PropTypes.number.isRequired,
|
|
||||||
dataNodes: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
|
|
||||||
cluster: PropTypes.shape({
|
|
||||||
data: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
|
||||||
meta: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
|
||||||
}),
|
|
||||||
clusterID: PropTypes.string.isRequired,
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {cluster, clusterID, dataNodes, refreshIntervalMs} = this.props;
|
|
||||||
const data = _.map(cluster.data, (node) => {
|
|
||||||
// The nodeID tag is for data nodes is currently the TCP address, but is fickle and
|
|
||||||
// possibly subject to change.
|
|
||||||
return {addr: node.httpAddr, type: 'data', nodeID: node.tcpAddr};
|
|
||||||
});
|
|
||||||
const meta = _.map(cluster.meta, (node) => {
|
|
||||||
return {addr: node.addr, type: 'meta'};
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="col-lg-8">
|
|
||||||
<div className="panel panel-minimal">
|
|
||||||
<div className="panel-heading">
|
|
||||||
<h2 className="panel-title">Data</h2>
|
|
||||||
</div>
|
|
||||||
<div className="panel-body">
|
|
||||||
<div className="table-responsive">
|
|
||||||
<table className="table v-center">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Node</th>
|
|
||||||
<th>Disk Used</th>
|
|
||||||
<th>Active Queries</th>
|
|
||||||
<th>Active Writes</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{data.map((n, i) => <NodeTableRow key={i} node={n} clusterID={clusterID} dataNodes={dataNodes} refreshIntervalMs={refreshIntervalMs}/>)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-lg-4">
|
|
||||||
<div className="panel panel-minimal">
|
|
||||||
<div className="panel-heading">
|
|
||||||
<h2 className="panel-title">Meta</h2>
|
|
||||||
</div>
|
|
||||||
<div className="panel-body">
|
|
||||||
<div className="">
|
|
||||||
<table className="table v-center js-node-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Node</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{meta.map((n, i) => <NodeTableRow key={i} node={n} clusterID={clusterID} dataNodes={dataNodes} refreshIntervalMs={refreshIntervalMs}/>)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default NodeTable;
|
|
|
@ -1,76 +0,0 @@
|
||||||
import React, {PropTypes} from 'react';
|
|
||||||
import {formatBytes} from 'utils/formatting';
|
|
||||||
import MiniGraph from 'shared/components/MiniGraph';
|
|
||||||
import AutoRefresh from 'shared/components/AutoRefresh';
|
|
||||||
import {nodeDiskUsage} from 'shared/apis/stats';
|
|
||||||
import {diskBytesFromShard} from 'shared/parsing/diskBytes';
|
|
||||||
const RefreshingMiniGraph = AutoRefresh(MiniGraph);
|
|
||||||
import {OVERVIEW_TIME_RANGE, OVERVIEW_INTERVAL} from '../constants';
|
|
||||||
|
|
||||||
const {string, shape, number} = PropTypes;
|
|
||||||
const NodeTableRow = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
refreshIntervalMs: number.isRequired,
|
|
||||||
dataNodes: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
|
|
||||||
clusterID: string.isRequired,
|
|
||||||
node: shape({
|
|
||||||
addr: string.isRequired,
|
|
||||||
type: string.isRequired,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState() {
|
|
||||||
return {
|
|
||||||
diskUsed: 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
if (this.props.node.type !== 'data') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {node, dataNodes, clusterID} = this.props;
|
|
||||||
|
|
||||||
nodeDiskUsage(dataNodes, clusterID, node.nodeID).then((resp) => {
|
|
||||||
this.setState({
|
|
||||||
diskUsed: diskBytesFromShard(resp.data).bytes,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {node, dataNodes, clusterID, refreshIntervalMs} = this.props;
|
|
||||||
const diskUsed = formatBytes(this.state.diskUsed);
|
|
||||||
|
|
||||||
const activeQueries = `SELECT NON_NEGATIVE_DERIVATIVE(max(queryReq)) FROM httpd WHERE time > now() - ${OVERVIEW_TIME_RANGE} AND nodeID='${node.nodeID}' AND clusterID='${clusterID}' GROUP by time(${OVERVIEW_INTERVAL})`;
|
|
||||||
const activeWrites = `SELECT NON_NEGATIVE_DERIVATIVE(max(req)) FROM "write" WHERE time > now() - ${OVERVIEW_TIME_RANGE} AND nodeID='${node.nodeID}' AND clusterID='${clusterID}' GROUP BY time(${OVERVIEW_INTERVAL})`;
|
|
||||||
|
|
||||||
if (node.type !== 'data') {
|
|
||||||
return (
|
|
||||||
<tr className="node">
|
|
||||||
<td>{node.addr}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr className="node">
|
|
||||||
<td>{node.addr}</td>
|
|
||||||
<td>{diskUsed}</td>
|
|
||||||
<td>
|
|
||||||
<RefreshingMiniGraph clusterID={clusterID} queries={[{host: dataNodes, database: '_internal', text: activeQueries}]} autoRefresh={refreshIntervalMs}>
|
|
||||||
<p className="cluster-stat-empty"><span className="icon cubo"></span> Waiting for stats...</p>
|
|
||||||
</RefreshingMiniGraph>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<RefreshingMiniGraph clusterID={clusterID} queries={[{host: dataNodes, database: '_internal', text: activeWrites}]} autoRefresh={refreshIntervalMs}>
|
|
||||||
<p className="cluster-stat-empty"><span className="icon cubo"></span> Waiting for stats..</p>
|
|
||||||
</RefreshingMiniGraph>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default NodeTableRow;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export const OVERVIEW_TIME_RANGE = '1h';
|
|
||||||
export const OVERVIEW_INTERVAL = '30s';
|
|
|
@ -1,77 +0,0 @@
|
||||||
import React, {PropTypes} from 'react';
|
|
||||||
import NodeTable from '../components/NodeTable';
|
|
||||||
import MiscStatsPanel from '../components/MiscStatsPanel';
|
|
||||||
import ClusterStatsPanel from '../components/ClusterStatsPanel';
|
|
||||||
import FlashMessages from 'shared/components/FlashMessages';
|
|
||||||
import {showCluster} from 'shared/apis';
|
|
||||||
|
|
||||||
export const OverviewPage = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
params: PropTypes.shape({
|
|
||||||
clusterID: PropTypes.string.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
dataNodes: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
|
|
||||||
addFlashMessage: PropTypes.func.isRequired, // Injected by the `FlashMessages` wrapper
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState() {
|
|
||||||
return {
|
|
||||||
isFetching: true,
|
|
||||||
// Raw output of plutonium's `/show-cluster` - includes both meta
|
|
||||||
// and data node information.
|
|
||||||
cluster: {},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const {clusterID} = this.props.params;
|
|
||||||
showCluster(clusterID).then((resp) => {
|
|
||||||
this.setState({cluster: resp.data});
|
|
||||||
}).catch(() => {
|
|
||||||
this.props.addFlashMessage({
|
|
||||||
text: 'Something went wrong! Try refreshing your browser and email support@influxdata.com if the problem persists.',
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
}).then(() => {
|
|
||||||
this.setState({isFetching: false});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.isFetching) {
|
|
||||||
return <div className="page-spinner" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {cluster} = this.state;
|
|
||||||
const {dataNodes, params: {clusterID}} = this.props;
|
|
||||||
const clusterPanelRefreshMs = 10000;
|
|
||||||
const nodeTableRefreshMs = 10000;
|
|
||||||
const miscPanelRefreshMs = 30000;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="overview">
|
|
||||||
<div className="enterprise-header">
|
|
||||||
<div className="enterprise-header__container">
|
|
||||||
<div className="enterprise-header__left">
|
|
||||||
<h1>
|
|
||||||
Cluster Overiew
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="container-fluid">
|
|
||||||
<div className="row">
|
|
||||||
<ClusterStatsPanel clusterID={clusterID} refreshIntervalMs={clusterPanelRefreshMs} dataNodes={dataNodes} />
|
|
||||||
<MiscStatsPanel clusterID={clusterID} refreshIntervalMs={miscPanelRefreshMs} dataNodes={dataNodes} />
|
|
||||||
</div>{/* /row */}
|
|
||||||
|
|
||||||
<div className="row">
|
|
||||||
<NodeTable dataNodes={dataNodes} cluster={cluster} clusterID={clusterID} refreshIntervalMs={nodeTableRefreshMs}/>
|
|
||||||
</div>
|
|
||||||
</div>{/* /container */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default FlashMessages(OverviewPage);
|
|
|
@ -1,2 +0,0 @@
|
||||||
import OverviewPage from './containers/OverviewPage';
|
|
||||||
export default OverviewPage;
|
|
|
@ -1,68 +0,0 @@
|
||||||
import u from 'updeep';
|
|
||||||
|
|
||||||
export default function hosts(state = {}, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'ADD_HOST': {
|
|
||||||
const {url, params} = action.payload;
|
|
||||||
|
|
||||||
const update = {
|
|
||||||
[url]: {
|
|
||||||
nickname: params.nickname,
|
|
||||||
host: params.host,
|
|
||||||
port: params.port,
|
|
||||||
ssl: params.ssl,
|
|
||||||
username: params.username,
|
|
||||||
password: params.password,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return u(update, state);
|
|
||||||
}
|
|
||||||
case 'LOAD_HOST_DIAGNOSTICS': {
|
|
||||||
const allSeries = action.response.results[0].series;
|
|
||||||
|
|
||||||
const networkSeries = allSeries.find((s) => s.name === 'network');
|
|
||||||
const hostnameIndex = networkSeries.columns.indexOf('hostname');
|
|
||||||
const hostname = networkSeries.values[0][hostnameIndex];
|
|
||||||
|
|
||||||
const systemSeries = allSeries.find((s) => s.name === 'system');
|
|
||||||
const uptimeIndex = systemSeries.columns.indexOf('uptime');
|
|
||||||
const uptime = systemSeries.values[0][uptimeIndex];
|
|
||||||
|
|
||||||
const buildSeries = allSeries.find((s) => s.name === 'build');
|
|
||||||
const versionIndex = buildSeries.columns.indexOf('Version');
|
|
||||||
const version = buildSeries.values[0][versionIndex];
|
|
||||||
|
|
||||||
const update = {
|
|
||||||
[action.url]: {
|
|
||||||
diagnostics: {
|
|
||||||
hostname,
|
|
||||||
uptime,
|
|
||||||
version,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return u(update, state);
|
|
||||||
}
|
|
||||||
case 'DELETE_HOST': {
|
|
||||||
const stateCopy = Object.assign({}, state);
|
|
||||||
delete stateCopy[action.payload.url];
|
|
||||||
return stateCopy;
|
|
||||||
}
|
|
||||||
case 'LOAD_SERVERS_IN_CLUSTER': {
|
|
||||||
const {host, dataNodes, metaNodes} = action.payload;
|
|
||||||
|
|
||||||
const update = {
|
|
||||||
[host]: {
|
|
||||||
dataNodes,
|
|
||||||
metaNodes,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return u(update, state);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
import {combineReducers} from 'redux';
|
|
||||||
import time from './time';
|
|
||||||
import hosts from './hosts';
|
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
|
||||||
hosts,
|
|
||||||
time,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default rootReducer;
|
|
|
@ -1,26 +0,0 @@
|
||||||
import u from 'updeep';
|
|
||||||
|
|
||||||
export default function time(state = {}, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'SET_TIME_BOUNDS': {
|
|
||||||
const update = {
|
|
||||||
bounds: action.payload.bounds,
|
|
||||||
groupByInterval: action.payload.groupByInterval || state.groupByInterval,
|
|
||||||
};
|
|
||||||
|
|
||||||
return u(update, state);
|
|
||||||
}
|
|
||||||
case 'SET_AUTO_REFRESH': {
|
|
||||||
const update = {
|
|
||||||
autoRefresh: action.payload.milliseconds,
|
|
||||||
};
|
|
||||||
|
|
||||||
return u(update, state);
|
|
||||||
}
|
|
||||||
case 'SET_GROUP_BY': {
|
|
||||||
return u({groupByInterval: action.payload.groupByInterval}, state);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
import {createStore, applyMiddleware} from 'redux';
|
|
||||||
import thunkMiddleware from 'redux-thunk';
|
|
||||||
import rootReducer from '../reducers';
|
|
||||||
|
|
||||||
import makeAppStorage from 'shared/middleware/appStorage';
|
|
||||||
import makeQueryExecuter from 'shared/middleware/queryExecuter';
|
|
||||||
|
|
||||||
export default function configureStore(effectiveWindow, initialState) {
|
|
||||||
return createStore(
|
|
||||||
rootReducer,
|
|
||||||
initialState,
|
|
||||||
applyMiddleware(
|
|
||||||
thunkMiddleware,
|
|
||||||
makeAppStorage(effectiveWindow.localStorage),
|
|
||||||
makeQueryExecuter()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -27,7 +27,6 @@ const SideNav = React.createClass({
|
||||||
</NavBlock>
|
</NavBlock>
|
||||||
<NavBlock matcher="overview" icon="crown" link={`${sourcePrefix}/overview`}>
|
<NavBlock matcher="overview" icon="crown" link={`${sourcePrefix}/overview`}>
|
||||||
<NavHeader link={`${sourcePrefix}/overview`} title="Sources" />
|
<NavHeader link={`${sourcePrefix}/overview`} title="Sources" />
|
||||||
<NavListItem matcher="overview" link={`${sourcePrefix}/overview`}>Overview</NavListItem>
|
|
||||||
<NavListItem matcher="sources$" link={`/sources`}>Manage Sources</NavListItem>
|
<NavListItem matcher="sources$" link={`/sources`}>Manage Sources</NavListItem>
|
||||||
<NavListItem matcher="queries" link={`${sourcePrefix}/queries`}>Queries</NavListItem>
|
<NavListItem matcher="queries" link={`${sourcePrefix}/queries`}>Queries</NavListItem>
|
||||||
<NavListItem matcher="tasks" link={`${sourcePrefix}/tasks`}>Tasks</NavListItem>
|
<NavListItem matcher="tasks" link={`${sourcePrefix}/tasks`}>Tasks</NavListItem>
|
||||||
|
|
|
@ -1,132 +0,0 @@
|
||||||
.cluster-stat-tiles {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
.cluster-stat-tile {
|
|
||||||
line-height: 1.75;
|
|
||||||
width: 49%;
|
|
||||||
}
|
|
||||||
$cluster-stat-height: 44px;
|
|
||||||
$cluster-stat-padding: 2em;
|
|
||||||
@keyframes cluster-spinner {
|
|
||||||
0% {transform: rotate(0deg);}
|
|
||||||
100% {transform: rotate(360deg);}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cluster-stat {
|
|
||||||
display: flex !important;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: $cluster-stat-padding 0;
|
|
||||||
align-items: center;
|
|
||||||
height: $cluster-stat-height;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
|
|
||||||
div {
|
|
||||||
div {
|
|
||||||
min-width: 150px;
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&-empty {
|
|
||||||
width: 100%;
|
|
||||||
background-color: $g19-ghost;
|
|
||||||
height: $cluster-stat-height;
|
|
||||||
margin: 0;
|
|
||||||
line-height: $cluster-stat-height;
|
|
||||||
text-align: center;
|
|
||||||
border-bottom: 2px solid $g18-cloud;
|
|
||||||
border-top: 2px solid $g20-white;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-style: italic;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
animation: cluster-spinner 3.8s infinite linear;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
position: relative;
|
|
||||||
top: -1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&--label {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
padding-left: 1.5em;
|
|
||||||
}
|
|
||||||
&-2x {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: $cluster-stat-padding 0;
|
|
||||||
align-items: center;
|
|
||||||
height: $cluster-stat-height;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
&--label,
|
|
||||||
&--number {
|
|
||||||
vertical-align: middle;
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 1em;
|
|
||||||
}
|
|
||||||
&--number {
|
|
||||||
// font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.influx-version,
|
|
||||||
.disk-util {
|
|
||||||
border: 2px solid $g18-cloud;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 10px;
|
|
||||||
}
|
|
||||||
.quarter-table-width {
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
.js-node-table {
|
|
||||||
margin-bottom: 0;
|
|
||||||
tbody tr td {
|
|
||||||
height: $cluster-stat-height + 16px;
|
|
||||||
}
|
|
||||||
.cluster-stat {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.btn.rebalance {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
div {
|
|
||||||
#rebalance {
|
|
||||||
margin-left: 5px;
|
|
||||||
font-size: 20px;
|
|
||||||
animation-name: spin;
|
|
||||||
animation-duration: 4000ms;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
from {
|
|
||||||
transform:rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform:rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tasks__popover {
|
|
||||||
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
|
||||||
box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
|
||||||
position: absolute;
|
|
||||||
width: auto;
|
|
||||||
background: #FFFFFF;
|
|
||||||
right: 10px;
|
|
||||||
top: 53px;
|
|
||||||
z-index: 1;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
Loading…
Reference in New Issue