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 CheckDataNodes from 'src/CheckDataNodes';
|
||||
import {HostsPage, HostPage} from 'src/hosts';
|
||||
import OverviewPage from 'src/overview';
|
||||
import QueriesPage from 'src/queries';
|
||||
import TasksPage from 'src/tasks';
|
||||
import RetentionPoliciesPage from 'src/retention_policies';
|
||||
|
@ -122,7 +121,6 @@ const Root = React.createClass({
|
|||
<Route path="/sources" component={SelectSourcePage} />
|
||||
<Route path="/sources/:sourceID" component={App}>
|
||||
<Route component={CheckDataNodes}>
|
||||
<Route path="overview" component={OverviewPage} />
|
||||
<Route path="queries" component={QueriesPage} />
|
||||
<Route path="accounts" component={ClusterAccountsPage} />
|
||||
<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 matcher="overview" icon="crown" link={`${sourcePrefix}/overview`}>
|
||||
<NavHeader link={`${sourcePrefix}/overview`} title="Sources" />
|
||||
<NavListItem matcher="overview" link={`${sourcePrefix}/overview`}>Overview</NavListItem>
|
||||
<NavListItem matcher="sources$" link={`/sources`}>Manage Sources</NavListItem>
|
||||
<NavListItem matcher="queries" link={`${sourcePrefix}/queries`}>Queries</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