WIP add kubernetes dashboard
parent
b66526b483
commit
01ae8b1b82
|
@ -7,6 +7,7 @@ import App from 'src/App';
|
|||
import AlertsApp from 'src/alerts';
|
||||
import CheckSources from 'src/CheckSources';
|
||||
import {HostsPage, HostPage} from 'src/hosts';
|
||||
import {KubernetesPage} from 'src/kubernetes';
|
||||
import {KapacitorPage, KapacitorRulePage, KapacitorRulesPage, KapacitorTasksPage} from 'src/kapacitor';
|
||||
import TasksPage from 'src/tasks';
|
||||
import DataExplorer from 'src/chronograf';
|
||||
|
@ -100,6 +101,7 @@ const Root = React.createClass({
|
|||
<Route path="chronograf/data-explorer/:base64ExplorerID" component={DataExplorer} />
|
||||
<Route path="hosts" component={HostsPage} />
|
||||
<Route path="hosts/:hostID" component={HostPage} />
|
||||
<Route path="kubernetes" component={KubernetesPage} />
|
||||
<Route path="kapacitor-config" component={KapacitorPage} />
|
||||
<Route path="kapacitor-tasks" component={KapacitorTasksPage} />
|
||||
<Route path="alerts" component={AlertsApp} />
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import {proxy} from 'utils/queryUrlGenerator';
|
||||
import AJAX from 'utils/ajax';
|
||||
import _ from 'lodash';
|
||||
|
||||
export function getAppsForHosts(proxyLink, hosts, appMappings) {
|
||||
const measurements = appMappings.map((m) => `^${m.measurement}$`).join('|');
|
||||
const measurementsToApps = _.zipObject(appMappings.map(m => m.measurement), appMappings.map(m => m.name));
|
||||
return proxy({
|
||||
source: proxyLink,
|
||||
query: `show series from /${measurements}/`,
|
||||
db: 'telegraf',
|
||||
}).then((resp) => {
|
||||
const newHosts = Object.assign({}, hosts);
|
||||
const allSeries = _.get(resp, ['data', 'results', '0', 'series', '0', 'values'], []);
|
||||
allSeries.forEach(([series]) => {
|
||||
const matches = series.match(/(\w*).*,host=([^,]*)/);
|
||||
if (!matches || matches.length !== 3) { // eslint-disable-line no-magic-numbers
|
||||
return;
|
||||
}
|
||||
const measurement = matches[1];
|
||||
const host = matches[2];
|
||||
|
||||
if (!newHosts[host]) {
|
||||
return;
|
||||
}
|
||||
if (!newHosts[host].apps) {
|
||||
newHosts[host].apps = [];
|
||||
}
|
||||
newHosts[host].apps = _.uniq(newHosts[host].apps.concat(measurementsToApps[measurement]));
|
||||
});
|
||||
|
||||
return newHosts;
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchLayouts() {
|
||||
return AJAX({
|
||||
url: `/chronograf/v1/layouts`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import LayoutRenderer from '../components/LayoutRenderer';
|
||||
import TimeRangeDropdown from '../../shared/components/TimeRangeDropdown';
|
||||
import timeRanges from 'hson!../../shared/data/timeRanges.hson';
|
||||
|
||||
export const KubernetesPage = React.createClass({
|
||||
propTypes: {
|
||||
source: PropTypes.shape({
|
||||
links: PropTypes.shape({
|
||||
proxy: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}),
|
||||
layouts: PropTypes.arrayOf(PropTypes.shape().isRequired).isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
const fifteenMinutesIndex = 1;
|
||||
return {
|
||||
timeRange: timeRanges[fifteenMinutesIndex],
|
||||
};
|
||||
},
|
||||
|
||||
renderLayouts(layouts) {
|
||||
const autoRefreshMs = 15000;
|
||||
const {timeRange} = this.state;
|
||||
const source = this.props.source.links.proxy;
|
||||
|
||||
let layoutCells = [];
|
||||
layouts.forEach((layout) => {
|
||||
layoutCells = layoutCells.concat(layout.cells);
|
||||
});
|
||||
|
||||
layoutCells.forEach((cell, i) => {
|
||||
cell.queries.forEach((q) => {
|
||||
q.text = q.query;
|
||||
q.database = q.db;
|
||||
});
|
||||
cell.x = (i * 4 % 12); // eslint-disable-line no-magic-numbers
|
||||
cell.y = 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<LayoutRenderer
|
||||
timeRange={timeRange}
|
||||
cells={layoutCells}
|
||||
autoRefreshMs={autoRefreshMs}
|
||||
source={source}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
handleChooseTimeRange({lower}) {
|
||||
const timeRange = timeRanges.find((range) => range.queryValue === lower);
|
||||
this.setState({timeRange});
|
||||
},
|
||||
|
||||
render() {
|
||||
const {layouts} = this.props;
|
||||
const {timeRange} = this.state;
|
||||
|
||||
return (
|
||||
<div className="host-dashboard hosts-page">
|
||||
<div className="enterprise-header hosts-dashboard-header">
|
||||
<div className="enterprise-header__container">
|
||||
<div className="enterprise-header__left">
|
||||
<h2>Kubernetes Dashboard</h2>
|
||||
</div>
|
||||
<div className="enterprise-header__right">
|
||||
<h1>Range:</h1>
|
||||
<TimeRangeDropdown onChooseTimeRange={this.handleChooseTimeRange} selected={timeRange.inputValue} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hosts-page-scroll-container">
|
||||
<div className="container-fluid hosts-dashboard">
|
||||
<div className="row">
|
||||
{ (layouts.length > 0) ? this.renderLayouts(layouts) : '' }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
export default KubernetesPage;
|
|
@ -0,0 +1,109 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import AutoRefresh from 'shared/components/AutoRefresh';
|
||||
import LineGraph from 'shared/components/LineGraph';
|
||||
import ReactGridLayout from 'react-grid-layout';
|
||||
import _ from 'lodash';
|
||||
|
||||
const RefreshingLineGraph = AutoRefresh(LineGraph);
|
||||
|
||||
export const LayoutRenderer = React.createClass({
|
||||
propTypes: {
|
||||
timeRange: PropTypes.shape({
|
||||
defaultGroupBy: PropTypes.string.isRequired,
|
||||
queryValue: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
cells: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
queries: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
rp: PropTypes.string.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
database: PropTypes.string.isRequired,
|
||||
groupbys: PropTypes.arrayOf(PropTypes.string),
|
||||
wheres: PropTypes.arrayOf(PropTypes.string),
|
||||
}).isRequired
|
||||
).isRequired,
|
||||
x: PropTypes.number.isRequired,
|
||||
y: PropTypes.number.isRequired,
|
||||
w: PropTypes.number.isRequired,
|
||||
h: PropTypes.number.isRequired,
|
||||
i: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
autoRefreshMs: PropTypes.number.isRequired,
|
||||
host: PropTypes.string,
|
||||
source: PropTypes.string,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return ({
|
||||
layout: _.without(this.props.cells, ['queries']),
|
||||
});
|
||||
},
|
||||
|
||||
buildQuery(q) {
|
||||
const {timeRange, host} = this.props;
|
||||
const {wheres, groupbys} = q;
|
||||
|
||||
let text = q.text;
|
||||
|
||||
text += ` where time > ${timeRange.queryValue}`
|
||||
|
||||
if (host) {
|
||||
text += ` and \"host\" = '${host}'`;
|
||||
}
|
||||
|
||||
if (wheres && wheres.length > 0) {
|
||||
text += ` and ${wheres.join(' and ')}`;
|
||||
}
|
||||
|
||||
if (groupbys) {
|
||||
if (groupbys.find((g) => g.includes("time"))) {
|
||||
text += ` group by ${groupbys.join(',')}`;
|
||||
} else if (groupbys.length > 0) {
|
||||
text += ` group by time(${timeRange.defaultGroupBy}),${groupbys.join(',')}`;
|
||||
} else {
|
||||
text += ` group by time(${timeRange.defaultGroupBy})`;
|
||||
}
|
||||
} else {
|
||||
text += ` group by time(${timeRange.defaultGroupBy})`;
|
||||
}
|
||||
|
||||
return text;
|
||||
},
|
||||
|
||||
generateGraphs() {
|
||||
const {autoRefreshMs, source} = this.props;
|
||||
|
||||
return this.props.cells.map((cell) => {
|
||||
const qs = cell.queries.map((q) => {
|
||||
return Object.assign({}, q, {
|
||||
host: source,
|
||||
text: this.buildQuery(q),
|
||||
});
|
||||
});
|
||||
return (
|
||||
<div key={cell.i}>
|
||||
<h2 className="hosts-graph-heading">{cell.name}</h2>
|
||||
<div className="hosts-graph graph-panel__graph-container">
|
||||
<RefreshingLineGraph
|
||||
queries={qs}
|
||||
autoRefresh={autoRefreshMs}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ReactGridLayout layout={this.state.layout} isDraggable={false} isResizable={false} cols={12} rowHeight={90} width={1200}>
|
||||
{this.generateGraphs()}
|
||||
</ReactGridLayout>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default LayoutRenderer;
|
|
@ -0,0 +1,34 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import {fetchLayouts} from '../apis';
|
||||
import KubernetesDashboard from 'src/kubernetes/components/KubernetesDashboard';
|
||||
|
||||
export const KubernetesPage = React.createClass({
|
||||
propTypes: {
|
||||
source: PropTypes.shape({
|
||||
links: PropTypes.shape({
|
||||
proxy: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}),
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
layouts: [],
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
fetchLayouts().then(({data: {layouts}}) => {
|
||||
const kubernetesLayouts = layouts.filter((l) => l.app === 'kubernetes');
|
||||
this.setState({layouts: kubernetesLayouts});
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<KubernetesDashboard layouts={this.state.layouts} source={this.props.source} />
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default KubernetesPage;
|
|
@ -0,0 +1,2 @@
|
|||
import KubernetesPage from './containers/KubernetesPage';
|
||||
export {KubernetesPage};
|
|
@ -1,4 +1,5 @@
|
|||
import AJAX from 'utils/ajax';
|
||||
import {proxy} from 'utils/queryUrlGenerator';
|
||||
|
||||
export function getSources() {
|
||||
return AJAX({
|
||||
|
|
|
@ -23,6 +23,7 @@ const SideNav = React.createClass({
|
|||
<NavBlock icon="cpu" link={`${sourcePrefix}/hosts`}>
|
||||
<NavHeader link={`${sourcePrefix}/hosts`} title="Infrastructure" />
|
||||
<NavListItem link={`${sourcePrefix}/hosts`}>Host List</NavListItem>
|
||||
<NavListItem link={`${sourcePrefix}/kubernetes`}>Kubernetes Dashboard</NavListItem>
|
||||
</NavBlock>
|
||||
<NavBlock icon="graphline" link={dataExplorerLink}>
|
||||
<NavHeader link={dataExplorerLink} title={'Data'} />
|
||||
|
|
Loading…
Reference in New Issue