mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Convert status page to EUI (#21491)
* Convert the status_page plugin to EUI * Fix uiColor for disabled state
This commit is contained in:
parent
6af72b7d58
commit
33c6ade756
30 changed files with 989 additions and 453 deletions
|
@ -24,7 +24,8 @@ export default function (kibana) {
|
|||
title: 'Server Status',
|
||||
main: 'plugins/status_page/status_page',
|
||||
hidden: true,
|
||||
url: '/status'
|
||||
url: '/status',
|
||||
styleSheetPath: `${__dirname}/public/index.scss`
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`byte metric 1`] = `
|
||||
<EuiCard
|
||||
description="Heap Total"
|
||||
layout="horizontal"
|
||||
textAlign="center"
|
||||
title="1.50 GB"
|
||||
titleElement="span"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`float metric 1`] = `
|
||||
<EuiCard
|
||||
description="Load"
|
||||
layout="horizontal"
|
||||
textAlign="center"
|
||||
title="4.05, 3.37, 3.12"
|
||||
titleElement="span"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`general metric 1`] = `
|
||||
<EuiCard
|
||||
description="A metric"
|
||||
layout="horizontal"
|
||||
textAlign="center"
|
||||
title="1.80"
|
||||
titleElement="span"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`millisecond metric 1`] = `
|
||||
<EuiCard
|
||||
description="Response Time Max"
|
||||
layout="horizontal"
|
||||
textAlign="center"
|
||||
title="1234.00 ms"
|
||||
titleElement="span"
|
||||
/>
|
||||
`;
|
|
@ -0,0 +1,49 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`render 1`] = `
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="l"
|
||||
justifyContent="spaceBetween"
|
||||
responsive={true}
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 0,
|
||||
}
|
||||
}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiTitle
|
||||
size="m"
|
||||
>
|
||||
<h2>
|
||||
Kibana status is
|
||||
<EuiBadge
|
||||
color="secondary"
|
||||
iconSide="left"
|
||||
>
|
||||
Green
|
||||
</EuiBadge>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
grow={true}
|
||||
>
|
||||
<p>
|
||||
My Computer
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
`;
|
|
@ -0,0 +1,41 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`render 1`] = `
|
||||
<EuiBasicTable
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"field": "state",
|
||||
"name": "",
|
||||
"render": [Function],
|
||||
"width": "32px",
|
||||
},
|
||||
Object {
|
||||
"field": "id",
|
||||
"name": "ID",
|
||||
},
|
||||
Object {
|
||||
"field": "state",
|
||||
"name": "Status",
|
||||
"render": [Function],
|
||||
},
|
||||
]
|
||||
}
|
||||
data-test-subj="statusBreakdown"
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
"id": "plugin:1",
|
||||
"state": Object {
|
||||
"id": "green",
|
||||
"message": "Ready",
|
||||
"uiColor": "secondary",
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
noItemsMessage="No items found"
|
||||
responsive={true}
|
||||
rowProps={[Function]}
|
||||
/>
|
||||
`;
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import formatNumber from '../lib/format_number';
|
||||
import React, { Component } from 'react';
|
||||
import { Metric as MetricPropType } from '../lib/prop_types';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiFlexGrid,
|
||||
EuiFlexItem,
|
||||
EuiCard,
|
||||
} from '@elastic/eui';
|
||||
|
||||
|
||||
/*
|
||||
Displays a metric with the correct format.
|
||||
*/
|
||||
export class MetricTile extends Component {
|
||||
static propTypes = {
|
||||
metric: MetricPropType.isRequired
|
||||
};
|
||||
|
||||
formattedMetric() {
|
||||
const { value, type } = this.props.metric;
|
||||
|
||||
const metrics = [].concat(value);
|
||||
return metrics.map(function (metric) {
|
||||
return formatNumber(metric, type);
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name } = this.props.metric;
|
||||
|
||||
return (
|
||||
<EuiCard
|
||||
layout="horizontal"
|
||||
title={this.formattedMetric()}
|
||||
description={name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Wrapper component that simply maps each metric to MetricTile inside a FlexGroup
|
||||
*/
|
||||
const MetricTiles = ({
|
||||
metrics
|
||||
}) => (
|
||||
<EuiFlexGrid columns={3}>
|
||||
{
|
||||
metrics.map(metric => (
|
||||
<EuiFlexItem key={metric.name}>
|
||||
<MetricTile metric={metric} />
|
||||
</EuiFlexItem>
|
||||
))
|
||||
}
|
||||
</EuiFlexGrid>
|
||||
);
|
||||
|
||||
MetricTiles.propTypes = {
|
||||
metrics: PropTypes.arrayOf(MetricPropType).isRequired
|
||||
};
|
||||
|
||||
export default MetricTiles;
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { MetricTile } from './metric_tiles';
|
||||
|
||||
const GENERAL_METRIC = {
|
||||
name: 'A metric',
|
||||
value: 1.8
|
||||
// no type specified
|
||||
};
|
||||
|
||||
const BYTE_METRIC = {
|
||||
name: 'Heap Total',
|
||||
value: 1501560832,
|
||||
type: 'byte'
|
||||
};
|
||||
|
||||
const FLOAT_METRIC = {
|
||||
name: 'Load',
|
||||
type: 'float',
|
||||
value: [
|
||||
4.0537109375,
|
||||
3.36669921875,
|
||||
3.1220703125
|
||||
]
|
||||
};
|
||||
|
||||
const MS_METRIC = {
|
||||
name: 'Response Time Max',
|
||||
type: 'ms',
|
||||
value: 1234
|
||||
};
|
||||
|
||||
test('general metric', () => {
|
||||
const component = shallow(<MetricTile
|
||||
metric={GENERAL_METRIC}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
||||
test('byte metric', () => {
|
||||
const component = shallow(<MetricTile
|
||||
metric={BYTE_METRIC}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
||||
test('float metric', () => {
|
||||
const component = shallow(<MetricTile
|
||||
metric={FLOAT_METRIC}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
||||
test('millisecond metric', () => {
|
||||
const component = shallow(<MetricTile
|
||||
metric={MS_METRIC}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { State as StatePropType } from '../lib/prop_types';
|
||||
import {
|
||||
EuiText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiTitle,
|
||||
EuiBadge,
|
||||
} from '@elastic/eui';
|
||||
|
||||
const ServerState = ({
|
||||
name,
|
||||
serverState
|
||||
}) => (
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="spaceBetween"
|
||||
style={{ flexGrow: 0 }}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
{'Kibana status is '}
|
||||
<EuiBadge color={serverState.uiColor}>
|
||||
{serverState.title }
|
||||
</EuiBadge>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<p>
|
||||
{name}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
ServerState.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
serverState: StatePropType.isRequired
|
||||
};
|
||||
|
||||
export default ServerState;
|
|
@ -17,27 +17,20 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import formatNumber from './lib/format_number';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import statusPageMetricTemplate from './status_page_metric.html';
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import ServerStatus from './server_status';
|
||||
|
||||
uiModules
|
||||
.get('kibana', [])
|
||||
.filter('statusMetric', function () {
|
||||
return function (input, type) {
|
||||
const metrics = [].concat(input);
|
||||
return metrics.map(function (metric) {
|
||||
return formatNumber(metric, type);
|
||||
}).join(', ');
|
||||
};
|
||||
})
|
||||
.directive('statusPageMetric', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: statusPageMetricTemplate,
|
||||
scope: {
|
||||
metric: '=',
|
||||
},
|
||||
controllerAs: 'metric'
|
||||
};
|
||||
});
|
||||
const STATE = {
|
||||
id: 'green',
|
||||
title: 'Green',
|
||||
uiColor: 'secondary'
|
||||
};
|
||||
|
||||
test('render', () => {
|
||||
const component = shallow(<ServerStatus
|
||||
serverState={STATE}
|
||||
name="My Computer"
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
137
src/core_plugins/status_page/public/components/status_app.js
Normal file
137
src/core_plugins/status_page/public/components/status_app.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import loadStatus from '../lib/load_status';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiLoadingSpinner,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiPage,
|
||||
EuiPageBody,
|
||||
EuiPageContent,
|
||||
EuiSpacer,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import MetricTiles from './metric_tiles';
|
||||
import StatusTable from './status_table';
|
||||
import ServerStatus from './server_status';
|
||||
|
||||
class StatusApp extends Component {
|
||||
static propTypes = {
|
||||
buildNum: PropTypes.number.isRequired,
|
||||
buildSha: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
loading: true,
|
||||
fetchError: false,
|
||||
data: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount = async function () {
|
||||
const data = await loadStatus();
|
||||
|
||||
if (data) {
|
||||
this.setState({ loading: false, data: data });
|
||||
} else {
|
||||
this.setState({ fetchError: true, loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { buildNum, buildSha } = this.props;
|
||||
const { loading, fetchError, data } = this.state;
|
||||
|
||||
// If we're still loading, return early with a spinner
|
||||
if (loading) {
|
||||
return (
|
||||
<EuiLoadingSpinner size="l" />
|
||||
);
|
||||
}
|
||||
|
||||
if (fetchError) {
|
||||
return (
|
||||
<EuiText color="danger">An error occurred loading the status</EuiText>
|
||||
);
|
||||
}
|
||||
|
||||
// Extract the items needed to render each component
|
||||
const { metrics, statuses, serverState, name } = data;
|
||||
|
||||
return (
|
||||
<EuiPage className="stsPage">
|
||||
<EuiPageBody restrictWidth>
|
||||
|
||||
<ServerStatus
|
||||
name={name}
|
||||
serverState={serverState}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<MetricTiles metrics={metrics} />
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiPageContent grow={0}>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h2>Plugin status</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
BUILD <strong>{ buildNum }</strong>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
COMMIT <strong>{ buildSha }</strong>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<StatusTable statuses={statuses} />
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default StatusApp;
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { State as StatePropType } from '../lib/prop_types';
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
|
||||
|
||||
class StatusTable extends Component {
|
||||
static propTypes = {
|
||||
statuses: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired, // plugin id
|
||||
state: StatePropType.isRequired // state of the plugin
|
||||
})) // can be null
|
||||
};
|
||||
|
||||
static columns = [{
|
||||
field: 'state',
|
||||
name: '',
|
||||
render: state => <EuiIcon type="dot" ariaabel="" color={state.uiColor} />,
|
||||
width: '32px'
|
||||
}, {
|
||||
field: 'id',
|
||||
name: 'ID',
|
||||
}, {
|
||||
field: 'state',
|
||||
name: 'Status',
|
||||
render: state => <span>{ state.message }</span>
|
||||
}];
|
||||
|
||||
static getRowProps = ({ state }) => {
|
||||
return {
|
||||
className: `status-table-row-${state.uiColor}`
|
||||
};
|
||||
};
|
||||
|
||||
render() {
|
||||
const { statuses } = this.props;
|
||||
|
||||
if (!statuses) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiBasicTable
|
||||
columns={StatusTable.columns}
|
||||
items={statuses}
|
||||
rowProps={StatusTable.getRowProps}
|
||||
data-test-subj="statusBreakdown"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default StatusTable;
|
|
@ -17,31 +17,29 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import StatusTable from './status_table';
|
||||
|
||||
import formatNumber from './format_number';
|
||||
|
||||
export default function makeChartOptions(type) {
|
||||
return {
|
||||
chart: {
|
||||
type: 'lineChart',
|
||||
height: 200,
|
||||
showLegend: false,
|
||||
showXAxis: false,
|
||||
showYAxis: false,
|
||||
useInteractiveGuideline: true,
|
||||
tooltips: true,
|
||||
pointSize: 0,
|
||||
color: ['#444', '#777', '#aaa'],
|
||||
margin: {
|
||||
top: 10,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 20
|
||||
},
|
||||
xAxis: { tickFormat: function (d) { return formatNumber(d, 'time'); } },
|
||||
yAxis: { tickFormat: function (d) { return formatNumber(d, type); }, },
|
||||
y: function (d) { return d.y; },
|
||||
x: function (d) { return d.x; }
|
||||
}
|
||||
};
|
||||
}
|
||||
const STATE = {
|
||||
id: 'green',
|
||||
uiColor: 'secondary',
|
||||
message: 'Ready'
|
||||
};
|
||||
|
||||
|
||||
test('render', () => {
|
||||
const component = shallow(<StatusTable
|
||||
statuses={[
|
||||
{ id: 'plugin:1', state: STATE }
|
||||
]}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
||||
|
||||
test('render empty', () => {
|
||||
const component = shallow(<StatusTable />);
|
||||
expect(component.isEmptyRender()).toBe(true); // eslint-disable-line
|
||||
});
|
6
src/core_plugins/status_page/public/index.scss
Normal file
6
src/core_plugins/status_page/public/index.scss
Normal file
|
@ -0,0 +1,6 @@
|
|||
@import '../../../ui/public/styles/styling_constants';
|
||||
|
||||
// SASSTODO: Remove when K7 applies background color to body
|
||||
.stsPage {
|
||||
min-height: 100vh;
|
||||
}
|
|
@ -18,15 +18,12 @@
|
|||
*/
|
||||
|
||||
|
||||
import moment from 'moment';
|
||||
import numeral from 'numeral';
|
||||
|
||||
export default function formatNumber(num, which) {
|
||||
let format = '0.00';
|
||||
let postfix = '';
|
||||
switch (which) {
|
||||
case 'time':
|
||||
return moment(num).format('HH:mm:ss');
|
||||
case 'byte':
|
||||
format += ' b';
|
||||
break;
|
||||
|
@ -37,5 +34,6 @@ export default function formatNumber(num, which) {
|
|||
format = '0';
|
||||
break;
|
||||
}
|
||||
|
||||
return numeral(num).format(format) + postfix;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import formatNumber from './format_number';
|
||||
|
||||
describe('format byte', () => {
|
||||
test('zero', () => {
|
||||
expect(formatNumber(0, 'byte')).toEqual('0.00 B');
|
||||
});
|
||||
|
||||
test('mb', () => {
|
||||
expect(formatNumber(181142512, 'byte')).toEqual('181.14 MB');
|
||||
});
|
||||
|
||||
test('gb', () => {
|
||||
expect(formatNumber(273727485000, 'byte')).toEqual('273.73 GB');
|
||||
});
|
||||
});
|
||||
|
||||
describe('format ms', () => {
|
||||
test('zero', () => {
|
||||
expect(formatNumber(0, 'ms')).toEqual('0.00 ms');
|
||||
});
|
||||
|
||||
test('sub ms', () => {
|
||||
expect(formatNumber(0.128, 'ms')).toEqual('0.13 ms');
|
||||
});
|
||||
|
||||
test('many ms', () => {
|
||||
expect(formatNumber(3030.284, 'ms')).toEqual('3030.28 ms');
|
||||
});
|
||||
});
|
||||
|
||||
describe('format integer', () => {
|
||||
test('zero', () => {
|
||||
expect(formatNumber(0, 'integer')).toEqual('0');
|
||||
});
|
||||
|
||||
test('sub integer', () => {
|
||||
expect(formatNumber(0.728, 'integer')).toEqual('1');
|
||||
});
|
||||
|
||||
test('many integer', () => {
|
||||
expect(formatNumber(3030.284, 'integer')).toEqual('3030');
|
||||
});
|
||||
});
|
116
src/core_plugins/status_page/public/lib/load_status.js
Normal file
116
src/core_plugins/status_page/public/lib/load_status.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
import { notify } from 'ui/notify';
|
||||
|
||||
// Module-level error returned by notify.error
|
||||
let errorNotif;
|
||||
|
||||
/*
|
||||
Returns an object of any keys that should be included for metrics.
|
||||
*/
|
||||
function formatMetrics(data) {
|
||||
if (!data.metrics) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'Heap total',
|
||||
value: _.get(data.metrics, 'process.memory.heap.size_limit'),
|
||||
type: 'byte'
|
||||
}, {
|
||||
name: 'Heap used',
|
||||
value: _.get(data.metrics, 'process.memory.heap.used_in_bytes'),
|
||||
type: 'byte'
|
||||
}, {
|
||||
name: 'Load',
|
||||
value: [
|
||||
_.get(data.metrics, 'os.load.1m'),
|
||||
_.get(data.metrics, 'os.load.5m'),
|
||||
_.get(data.metrics, 'os.load.15m')
|
||||
],
|
||||
type: 'float'
|
||||
}, {
|
||||
name: 'Response time avg',
|
||||
value: _.get(data.metrics, 'response_times.avg_in_millis'),
|
||||
type: 'ms'
|
||||
}, {
|
||||
name: 'Response time max',
|
||||
value: _.get(data.metrics, 'response_times.max_in_millis'),
|
||||
type: 'ms'
|
||||
}, {
|
||||
name: 'Requests per second',
|
||||
value: _.get(data.metrics, 'requests.total') * 1000 / _.get(data.metrics, 'collection_interval_in_millis')
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
return fetch(
|
||||
chrome.addBasePath('/api/status'),
|
||||
{
|
||||
method: 'get',
|
||||
credentials: 'same-origin'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
Get the status from the server API and format it for display.
|
||||
|
||||
`fetchFn` can be injected for testing, defaults to the implementation above.
|
||||
*/
|
||||
async function loadStatus(fetchFn = fetchData) {
|
||||
// Clear any existing error banner.
|
||||
if (errorNotif) {
|
||||
errorNotif.clear();
|
||||
errorNotif = null;
|
||||
}
|
||||
|
||||
let response;
|
||||
|
||||
try {
|
||||
response = await fetchFn();
|
||||
} catch (e) {
|
||||
// If the fetch failed to connect, display an error and bail.
|
||||
errorNotif = notify.error('Failed to request server status. Perhaps your server is down?');
|
||||
return e;
|
||||
}
|
||||
|
||||
if (response.status >= 400) {
|
||||
// If the server does not respond with a successful status, display an error and bail.
|
||||
errorNotif = notify.error(`Failed to request server status with status code ${response.status}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return {
|
||||
name: data.name,
|
||||
statuses: data.status.statuses,
|
||||
serverState: data.status.overall.state,
|
||||
metrics: formatMetrics(data),
|
||||
};
|
||||
}
|
||||
|
||||
export default loadStatus;
|
110
src/core_plugins/status_page/public/lib/load_status.test.js
Normal file
110
src/core_plugins/status_page/public/lib/load_status.test.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import loadStatus from './load_status';
|
||||
|
||||
// Make importing the ui/notify module work in jest
|
||||
jest.mock('ui/metadata', () => ({
|
||||
metadata: {
|
||||
branch: 'my-metadata-branch',
|
||||
version: 'my-metadata-version'
|
||||
}
|
||||
}));
|
||||
|
||||
// A faked response to the `fetch` call
|
||||
const mockFetch = async () => ({
|
||||
status: 200,
|
||||
json: async () => ({
|
||||
name: 'My computer',
|
||||
status: {
|
||||
overall: {
|
||||
state: { id: 'yellow', title: 'Yellow' }
|
||||
},
|
||||
statuses: [
|
||||
{ id: 'plugin:1', state: { id: 'green' } },
|
||||
{ id: 'plugin:2', state: { id: 'yellow' } }
|
||||
],
|
||||
},
|
||||
metrics: {
|
||||
collection_interval_in_millis: 1000,
|
||||
os: { load: {
|
||||
'1m': 4.1,
|
||||
'5m': 2.1,
|
||||
'15m': 0.1,
|
||||
} },
|
||||
|
||||
process: { memory: { heap: {
|
||||
size_limit: 1000000,
|
||||
used_in_bytes: 100
|
||||
} } },
|
||||
|
||||
response_times: {
|
||||
avg_in_millis: 4000,
|
||||
max_in_millis: 8000
|
||||
},
|
||||
|
||||
requests: {
|
||||
total: 400
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
describe('response processing', () => {
|
||||
test('includes the name', async () => {
|
||||
const data = await loadStatus(mockFetch);
|
||||
expect(data.name).toEqual('My computer');
|
||||
});
|
||||
|
||||
test('includes the plugin statuses', async () => {
|
||||
const data = await loadStatus(mockFetch);
|
||||
expect(data.statuses).toEqual([
|
||||
{ id: 'plugin:1', state: { id: 'green' } },
|
||||
{ id: 'plugin:2', state: { id: 'yellow' } }
|
||||
]);
|
||||
});
|
||||
|
||||
test('includes the serverState', async () => {
|
||||
const data = await loadStatus(mockFetch);
|
||||
expect(data.serverState).toEqual({ id: 'yellow', title: 'Yellow' });
|
||||
});
|
||||
|
||||
test('builds the metrics', async () => {
|
||||
const data = await loadStatus(mockFetch);
|
||||
const names = data.metrics.map(m => m.name);
|
||||
expect(names).toEqual([
|
||||
'Heap total',
|
||||
'Heap used',
|
||||
'Load',
|
||||
'Response time avg',
|
||||
'Response time max',
|
||||
'Requests per second'
|
||||
]);
|
||||
|
||||
const values = data.metrics.map(m => m.value);
|
||||
expect(values).toEqual([
|
||||
1000000,
|
||||
100,
|
||||
[4.1, 2.1, 0.1],
|
||||
4000,
|
||||
8000,
|
||||
400
|
||||
]);
|
||||
});
|
||||
});
|
36
src/core_plugins/status_page/public/lib/prop_types.js
Normal file
36
src/core_plugins/status_page/public/lib/prop_types.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const State = PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
message: PropTypes.string, // optional
|
||||
title: PropTypes.string, // optional
|
||||
uiColor: PropTypes.string.isRequired,
|
||||
});
|
||||
|
||||
export const Metric = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.number),
|
||||
PropTypes.number
|
||||
]).isRequired,
|
||||
type: PropTypes.string // optional
|
||||
});
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
export default function readStatData(data, seriesNames) {
|
||||
// Metric Values format
|
||||
// metric: [[xValue, yValue], ...]
|
||||
// LoadMetric:
|
||||
// metric: [[xValue, [yValue, yValue2, yValue3]], ...]
|
||||
// return [
|
||||
// {type: 'line', key: name, yAxis: 1, values: [{x: xValue, y: yValue}, ...]},
|
||||
// {type: 'line', key: name, yAxis: 1, values: [{x: xValue, y: yValue1}, ...]},
|
||||
// {type: 'line', key: name, yAxis: 1, values: [{x: xValue, y: yValue2}, ...]}]
|
||||
//
|
||||
// Go through all of the metric values and split the values out.
|
||||
// returns an array of all of the averages
|
||||
|
||||
const metricList = [];
|
||||
seriesNames = seriesNames || [];
|
||||
data.forEach(function (vector) {
|
||||
vector = _.flatten(vector);
|
||||
const x = vector.shift();
|
||||
vector.forEach(function (yValue, i) {
|
||||
const series = seriesNames[i] || '';
|
||||
|
||||
if (!metricList[i]) {
|
||||
metricList[i] = {
|
||||
key: series,
|
||||
values: []
|
||||
};
|
||||
}
|
||||
// unshift to make sure they're in the correct order
|
||||
metricList[i].values.unshift({
|
||||
x: x,
|
||||
y: yValue
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return metricList;
|
||||
}
|
|
@ -1,52 +1 @@
|
|||
<div data-test-subj="statusPageContainer" class="container overall_state_default overall_state_{{ui.serverState}}">
|
||||
<header>
|
||||
<h1>
|
||||
Status: <span class="overall_state_color">{{ ui.serverStateMessage }}</span>
|
||||
<i class="fa overall_state_color state_icon" />
|
||||
<span class="pull-right">
|
||||
{{ ui.name }}
|
||||
</span>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<div class="row metrics_wrapper">
|
||||
<div ng-repeat="metric in ui.metrics">
|
||||
<status-page-metric metric="metric"></status-page-metric>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row statuses_wrapper">
|
||||
<h3>Status Breakdown</h3>
|
||||
|
||||
<div ng-if="!ui.statuses && ui.loading" class="statuses_loading">
|
||||
<span class="spinner"></span>
|
||||
</div>
|
||||
|
||||
<h4 ng-if="!ui.statuses && !ui.loading" class="statuses_missing">
|
||||
No status information available
|
||||
</h4>
|
||||
|
||||
<table class="statuses" data-test-subj="statusBreakdown" ng-if="ui.statuses">
|
||||
<tr class="row">
|
||||
<th class="col-xs-4" scope="col">ID</th>
|
||||
<th class="col-xs-8" scope="col">Status</th>
|
||||
</tr>
|
||||
<tr
|
||||
ng-repeat="status in ui.statuses"
|
||||
class="status status_state_default status_state_{{status.state}} row">
|
||||
|
||||
<td class="col-xs-4 status_id">{{status.id}}</td>
|
||||
<td class="col-xs-8 status_message">
|
||||
<i class="fa status_state_color status_state_icon" />
|
||||
{{status.message}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<footer class="row">
|
||||
<div class="col-xs-12 text-right build-info">
|
||||
Build {{::ui.buildInfo.num}}, Commit SHA {{::ui.buildInfo.sha}}
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<status-app build-num="{{ui.buildInfo.num}}" build-sha="'{{ui.buildInfo.sha}}'" />
|
||||
|
|
|
@ -17,91 +17,26 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { notify } from 'ui/notify';
|
||||
import 'ui/autoload/styles';
|
||||
import './status_page_metric';
|
||||
import './status_page.less';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import StatusApp from './components/status_app';
|
||||
|
||||
const chrome = require('ui/chrome')
|
||||
const app = uiModules.get('apps/status', []);
|
||||
app.directive('statusApp', function (reactDirective) {
|
||||
return reactDirective(StatusApp);
|
||||
});
|
||||
|
||||
chrome
|
||||
.setRootTemplate(require('plugins/status_page/status_page.html'))
|
||||
.setRootController('ui', function ($http, buildNum, buildSha) {
|
||||
const ui = this;
|
||||
ui.loading = false;
|
||||
|
||||
ui.buildInfo = {
|
||||
num: buildNum,
|
||||
sha: buildSha.substr(0, 8)
|
||||
};
|
||||
|
||||
ui.refresh = function () {
|
||||
ui.loading = true;
|
||||
|
||||
// go ahead and get the info you want
|
||||
return $http
|
||||
.get(chrome.addBasePath('/api/status'))
|
||||
.then(function (resp) {
|
||||
|
||||
if (ui.fetchError) {
|
||||
ui.fetchError.clear();
|
||||
ui.fetchError = null;
|
||||
}
|
||||
|
||||
const data = resp.data;
|
||||
const metrics = data.metrics;
|
||||
if (metrics) {
|
||||
ui.metrics = [{
|
||||
name: 'Heap Total',
|
||||
value: _.get(metrics, 'process.memory.heap.size_limit'),
|
||||
type: 'byte'
|
||||
}, {
|
||||
name: 'Heap Used',
|
||||
value: _.get(metrics, 'process.memory.heap.used_in_bytes'),
|
||||
type: 'byte'
|
||||
}, {
|
||||
name: 'Load',
|
||||
value: [
|
||||
_.get(metrics, 'os.load.1m'),
|
||||
_.get(metrics, 'os.load.5m'),
|
||||
_.get(metrics, 'os.load.15m')
|
||||
],
|
||||
type: 'float'
|
||||
}, {
|
||||
name: 'Response Time Avg',
|
||||
value: _.get(metrics, 'response_times.avg_in_millis'),
|
||||
type: 'ms'
|
||||
}, {
|
||||
name: 'Response Time Max',
|
||||
value: _.get(metrics, 'response_times.max_in_millis'),
|
||||
type: 'ms'
|
||||
}, {
|
||||
name: 'Requests Per Second',
|
||||
value: _.get(metrics, 'requests.total') * 1000 / _.get(metrics, 'collection_interval_in_millis')
|
||||
}];
|
||||
}
|
||||
|
||||
ui.name = data.name;
|
||||
ui.statuses = data.status.statuses;
|
||||
|
||||
const overall = data.status.overall;
|
||||
if (!ui.serverState || (ui.serverState !== overall.state)) {
|
||||
ui.serverState = overall.state;
|
||||
ui.serverStateMessage = overall.title;
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
if (ui.fetchError) return;
|
||||
ui.fetchError = notify.error('Failed to request server ui. Perhaps your server is down?');
|
||||
ui.metrics = ui.statuses = ui.overall = null;
|
||||
})
|
||||
.then(function () {
|
||||
ui.loading = false;
|
||||
});
|
||||
};
|
||||
|
||||
ui.refresh();
|
||||
});
|
||||
|
||||
uiModules.get('kibana')
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
@import "~font-awesome/less/font-awesome";
|
||||
|
||||
@status-bg: #eff0f2;
|
||||
@status-metric-bg: #fff;
|
||||
@status-metric-border: #aaa;
|
||||
@status-metric-title-color: #666;
|
||||
|
||||
@status-statuses-bg: #fff;
|
||||
@status-statuses-border: #bbb;
|
||||
@status-statuses-headings-color: #666;
|
||||
|
||||
@status-default: #7c7c7c;
|
||||
@status-green: #94c63d;
|
||||
@status-yellow: #edb800;
|
||||
@status-red: #da1e04;
|
||||
|
||||
@icon-default: @fa-var-clock-o;
|
||||
@icon-green: @fa-var-check;
|
||||
@icon-yellow: @fa-var-exclamation-circle;
|
||||
@icon-red: @fa-var-exclamation-triangle;
|
||||
|
||||
// background of main page
|
||||
.content {
|
||||
background-color: @status-bg;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom:15px;
|
||||
}
|
||||
|
||||
// metrics section
|
||||
.metrics_wrapper {
|
||||
margin-top: 25px;
|
||||
.status_metric_wrapper {
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
|
||||
.content {
|
||||
display: block;
|
||||
text-align: right;
|
||||
padding: 15px;
|
||||
padding-right: 20px;
|
||||
background-color: @status-metric-bg;
|
||||
border-top: 2px solid;
|
||||
border-top-color: @status-metric-border;
|
||||
|
||||
.title {
|
||||
color: @status-metric-title-color;
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
|
||||
.average {
|
||||
font-size: 42px;
|
||||
line-height:45px;
|
||||
font-weight: normal;
|
||||
margin:0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// status status table section
|
||||
.statuses_wrapper {
|
||||
margin-top: 25px;
|
||||
margin-left: -5px;
|
||||
margin-right: -5px;
|
||||
border-top:2px solid;
|
||||
background-color: @status-statuses-bg;
|
||||
padding: 10px;
|
||||
|
||||
h3 {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.statuses_loading,
|
||||
.statuses_missing {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.statuses {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.status {
|
||||
height:30px;
|
||||
line-height:30px;
|
||||
border-bottom:1px solid;
|
||||
border-bottom-color: @status-statuses-border;
|
||||
}
|
||||
|
||||
th {
|
||||
color:@status-statuses-headings-color;
|
||||
font-weight: normal;
|
||||
height:25px;
|
||||
line-height:25px;
|
||||
border-bottom:1px solid;
|
||||
border-bottom-color: @status-statuses-border;
|
||||
}
|
||||
|
||||
.status_id {
|
||||
padding:0px 5px;
|
||||
border-left: 2px solid;
|
||||
}
|
||||
|
||||
.status_message {
|
||||
padding:0;
|
||||
padding-left:15px;
|
||||
border-right: 2px solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//status state
|
||||
.status_state(@color, @icon) {
|
||||
.status_state_color {
|
||||
color: @color;
|
||||
}
|
||||
|
||||
.status_state_icon:before {
|
||||
content: @icon;
|
||||
}
|
||||
|
||||
.status_id {
|
||||
border-left-color: @color !important;
|
||||
}
|
||||
|
||||
.status_message {
|
||||
border-right-color: @color !important;
|
||||
}
|
||||
}
|
||||
|
||||
.status_state_default {
|
||||
.status_state(@status-default, @icon-default);
|
||||
}
|
||||
|
||||
.status_state_green {
|
||||
.status_state(@status-green, @icon-green);
|
||||
}
|
||||
|
||||
.status_state_yellow {
|
||||
.status_state(@status-yellow, @icon-yellow);
|
||||
}
|
||||
|
||||
.status_state_red {
|
||||
.status_state(@status-red, @icon-red);
|
||||
}
|
||||
|
||||
//server state
|
||||
.state(@color, @icon) {
|
||||
.overall_state_color {
|
||||
color: @color;
|
||||
}
|
||||
|
||||
.overall_state_icon:before {
|
||||
content: @icon;
|
||||
}
|
||||
|
||||
.statuses_wrapper {
|
||||
border-top-color: @color;
|
||||
}
|
||||
}
|
||||
|
||||
.overall_state_default {
|
||||
.state(@status-default, @icon-default);
|
||||
}
|
||||
|
||||
.overall_state_green {
|
||||
.state(@status-green, @icon-green);
|
||||
}
|
||||
|
||||
.overall_state_yellow {
|
||||
.state(@status-yellow, @icon-yellow);
|
||||
}
|
||||
|
||||
.overall_state_red {
|
||||
.state(@status-red, @icon-red);
|
||||
}
|
||||
|
||||
.build-info {
|
||||
color: #555;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
<div class="status_metric_wrapper col-md-4">
|
||||
<div class="content">
|
||||
<h3 class="title">{{ metric.name }}</h3>
|
||||
<h4 class="average">{{ metric.value | statusMetric: metric.type}}</h4>
|
||||
</div>
|
||||
</div>
|
|
@ -41,6 +41,6 @@ export function getKibanaInfoForStats(server, kbnServer) {
|
|||
transport_address: `${config.get('server.host')}:${config.get('server.port')}`,
|
||||
version: kbnServer.version.replace(snapshotRegex, ''),
|
||||
snapshot: snapshotRegex.test(kbnServer.version),
|
||||
status: get(status, 'overall.state')
|
||||
status: get(status, 'overall.id')
|
||||
};
|
||||
}
|
||||
|
|
|
@ -86,16 +86,18 @@ export default class ServerStatus {
|
|||
const since = _.get(_.sortBy(statuses, 'since'), [0, 'since']);
|
||||
|
||||
return {
|
||||
state: state.id,
|
||||
title: state.title,
|
||||
nickname: _.sample(state.nicknames),
|
||||
icon: state.icon,
|
||||
id: state.id,
|
||||
state: {
|
||||
title: state.title,
|
||||
uiColor: states.get(state.id).uiColor,
|
||||
nickname: _.sample(state.nicknames),
|
||||
},
|
||||
since: since,
|
||||
};
|
||||
}
|
||||
|
||||
isGreen() {
|
||||
return (this.overall().state === 'green');
|
||||
return (this.overall().id === 'green');
|
||||
}
|
||||
|
||||
notGreen() {
|
||||
|
|
|
@ -24,6 +24,7 @@ import * as states from './states';
|
|||
import Status from './status';
|
||||
import ServerStatus from './server_status';
|
||||
|
||||
|
||||
describe('ServerStatus class', function () {
|
||||
const plugin = { id: 'name', version: '1.2.3' };
|
||||
|
||||
|
@ -93,13 +94,13 @@ describe('ServerStatus class', function () {
|
|||
it('considers each status to produce a summary', function () {
|
||||
const status = serverStatus.createForPlugin(plugin);
|
||||
|
||||
expect(serverStatus.overall().state).toBe('uninitialized');
|
||||
expect(serverStatus.overall().id).toBe('uninitialized');
|
||||
|
||||
const match = function (overall, state) {
|
||||
expect(overall).toHaveProperty('state', state.id);
|
||||
expect(overall).toHaveProperty('title', state.title);
|
||||
expect(overall).toHaveProperty('icon', state.icon);
|
||||
expect(state.nicknames).toContain(overall.nickname);
|
||||
expect(overall).toHaveProperty('id', state.id);
|
||||
expect(overall).toHaveProperty('state.title', state.title);
|
||||
expect(overall).toHaveProperty('state.uiColor', state.uiColor);
|
||||
expect(state.nicknames).toContain(overall.state.nickname);
|
||||
};
|
||||
|
||||
status.green();
|
||||
|
@ -133,9 +134,12 @@ describe('ServerStatus class', function () {
|
|||
expect(json.statuses).toHaveLength(3);
|
||||
|
||||
const out = status => find(json.statuses, { id: status.id });
|
||||
expect(out(service)).toHaveProperty('state', 'green');
|
||||
expect(out(p1)).toHaveProperty('state', 'yellow');
|
||||
expect(out(p2)).toHaveProperty('state', 'red');
|
||||
expect(out(service)).toHaveProperty('state.message', 'Green');
|
||||
expect(out(service)).toHaveProperty('state.uiColor', 'secondary');
|
||||
expect(out(p1)).toHaveProperty('state.message', 'Yellow');
|
||||
expect(out(p1)).toHaveProperty('state.uiColor', 'warning');
|
||||
expect(out(p2)).toHaveProperty('state.message', 'Red');
|
||||
expect(out(p2)).toHaveProperty('state.uiColor', 'danger');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ export const all = [
|
|||
{
|
||||
id: 'red',
|
||||
title: 'Red',
|
||||
icon: 'danger',
|
||||
uiColor: 'danger',
|
||||
severity: 1000,
|
||||
nicknames: [
|
||||
'Danger Will Robinson! Danger!'
|
||||
|
@ -32,7 +32,7 @@ export const all = [
|
|||
{
|
||||
id: 'uninitialized',
|
||||
title: 'Uninitialized',
|
||||
icon: 'spinner',
|
||||
uiColor: 'default',
|
||||
severity: 900,
|
||||
nicknames: [
|
||||
'Initializing'
|
||||
|
@ -41,7 +41,7 @@ export const all = [
|
|||
{
|
||||
id: 'yellow',
|
||||
title: 'Yellow',
|
||||
icon: 'warning',
|
||||
uiColor: 'warning',
|
||||
severity: 800,
|
||||
nicknames: [
|
||||
'S.N.A.F.U',
|
||||
|
@ -52,7 +52,7 @@ export const all = [
|
|||
{
|
||||
id: 'green',
|
||||
title: 'Green',
|
||||
icon: 'success',
|
||||
uiColor: 'secondary',
|
||||
severity: 0,
|
||||
nicknames: [
|
||||
'Looking good'
|
||||
|
@ -62,7 +62,7 @@ export const all = [
|
|||
id: 'disabled',
|
||||
title: 'Disabled',
|
||||
severity: -1,
|
||||
icon: 'toggle-off',
|
||||
uiColor: 'default',
|
||||
nicknames: [
|
||||
'Am I even a thing?'
|
||||
]
|
||||
|
|
|
@ -55,9 +55,11 @@ export default class Status extends EventEmitter {
|
|||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
state: this.state,
|
||||
icon: states.get(this.state).icon,
|
||||
message: this.message,
|
||||
state: {
|
||||
id: this.state,
|
||||
message: this.message,
|
||||
uiColor: states.get(this.state).uiColor,
|
||||
},
|
||||
since: this.since
|
||||
};
|
||||
}
|
||||
|
|
|
@ -74,8 +74,8 @@ describe('Status class', function () {
|
|||
|
||||
const json = status.toJSON();
|
||||
expect(json.id).toEqual(status.id);
|
||||
expect(json.state).toEqual('green');
|
||||
expect(json.message).toEqual('Ready');
|
||||
expect(json.state.id).toEqual('green');
|
||||
expect(json.state.message).toEqual('Ready');
|
||||
});
|
||||
|
||||
it('should call on handler if status is already matched', function (done) {
|
||||
|
|
|
@ -36,13 +36,16 @@ export default function ({ getService }) {
|
|||
expect(body.version.build_number).to.be.a('number');
|
||||
|
||||
expect(body.status.overall).to.be.an('object');
|
||||
expect(body.status.overall.state).to.be('green');
|
||||
expect(body.status.overall.id).to.be('green');
|
||||
expect(body.status.overall.state).to.be.an('object');
|
||||
expect(body.status.overall.state.title).to.be('Green');
|
||||
|
||||
expect(body.status.statuses).to.be.an('array');
|
||||
const kibanaPlugin = body.status.statuses.find(s => {
|
||||
return s.id.indexOf('plugin:kibana') === 0;
|
||||
});
|
||||
expect(kibanaPlugin.state).to.be('green');
|
||||
expect(kibanaPlugin.state).to.be.an('object');
|
||||
expect(kibanaPlugin.state.id).to.be('green');
|
||||
|
||||
expect(body.metrics.collection_interval_in_millis).to.be.a('number');
|
||||
|
||||
|
|
|
@ -39,6 +39,6 @@ export class KibanaServerStatus {
|
|||
|
||||
async getOverallState() {
|
||||
const status = await this.get();
|
||||
return status.status.overall.state;
|
||||
return status.status.overall.id;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue