Skip to content

Commit

Permalink
node view (elastic#113318)
Browse files Browse the repository at this point in the history
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
neptunian and kibanamachine authored Sep 29, 2021
1 parent 9217be3 commit 682a1c1
Show file tree
Hide file tree
Showing 12 changed files with 508 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { useState } from 'react';
import { has } from 'lodash';

interface ParsedMonitoringData {
[key: string]: any;
}
export const useLocalStorage = <Value>(key: string, defaultValue: Value): [Value, Function] => {
const localStorageMonitoringKey = 'xpack.monitoring.data';
const getMonitoringDataStorage = () => {
const monitoringDataStorage = window.localStorage.getItem(localStorageMonitoringKey);
let parsedData: ParsedMonitoringData = {};
try {
parsedData = (monitoringDataStorage && JSON.parse(monitoringDataStorage)) || {};
} catch (e) {
throw new Error('Monitoring UI: error parsing locally stored monitoring data');
}
return parsedData;
};
const saveToStorage = (value: Value) => {
const monitoringDataObj = getMonitoringDataStorage();
monitoringDataObj[key] = value;
window.localStorage.setItem(localStorageMonitoringKey, JSON.stringify(monitoringDataObj));
};
const getFromStorage = (): Value | undefined => {
const monitoringDataObj = getMonitoringDataStorage();
if (has(monitoringDataObj, key)) {
return monitoringDataObj[key];
}
};

const storedItem = getFromStorage();
if (!storedItem) {
saveToStorage(defaultValue);
}
const toStore = storedItem || defaultValue;

const [item, setItem] = useState<Value>(toStore);

const saveItem = (value: Value) => {
saveToStorage(value);
setItem(value);
};

return [item, saveItem];
};
8 changes: 8 additions & 0 deletions x-pack/plugins/monitoring/public/application/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { BeatsOverviewPage } from './pages/beats/overview';
import { BeatsInstancesPage } from './pages/beats/instances';
import { CODE_PATH_ELASTICSEARCH, CODE_PATH_BEATS } from '../../common/constants';
import { ElasticsearchNodesPage } from './pages/elasticsearch/nodes_page';
import { ElasticsearchNodePage } from './pages/elasticsearch/node_page';
import { MonitoringTimeContainer } from './hooks/use_monitoring_time';
import { BreadcrumbContainer } from './hooks/use_breadcrumbs';

Expand Down Expand Up @@ -80,6 +81,13 @@ const MonitoringApp: React.FC<{
/>

{/* ElasticSearch Views */}
<RouteInit
path="/elasticsearch/nodes/:node"
component={ElasticsearchNodePage}
codePaths={[CODE_PATH_ELASTICSEARCH]}
fetchAllClusters={false}
/>

<RouteInit
path="/elasticsearch/nodes"
component={ElasticsearchNodesPage}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useContext, useState, useCallback } from 'react';
import { useParams } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import { find } from 'lodash';
import { ElasticsearchTemplate } from './elasticsearch_template';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { GlobalStateContext } from '../../global_state_context';
import { NodeReact } from '../../../components/elasticsearch';
import { ComponentProps } from '../../route_init';
import { SetupModeRenderer } from '../../setup_mode/setup_mode_renderer';
import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
import { useLocalStorage } from '../../hooks/use_local_storage';
import { useCharts } from '../../hooks/use_charts';
import { nodesByIndices } from '../../../components/elasticsearch/shard_allocation/transformers/nodes_by_indices';

interface SetupModeProps {
setupMode: any;
flyoutComponent: any;
bottomBarComponent: any;
}

export const ElasticsearchNodePage: React.FC<ComponentProps> = ({ clusters }) => {
const globalState = useContext(GlobalStateContext);
const { zoomInfo, onBrush } = useCharts();
const [showSystemIndices, setShowSystemIndices] = useLocalStorage<boolean>(
'showSystemIndices',
false
);

const { node }: { node: string } = useParams();
const { services } = useKibana<{ data: any }>();

const clusterUuid = globalState.cluster_uuid;
const ccs = globalState.ccs;
const cluster = find(clusters, {
cluster_uuid: clusterUuid,
});
const [data, setData] = useState({} as any);
const [nodesByIndicesData, setNodesByIndicesData] = useState([]);

const title = i18n.translate('xpack.monitoring.elasticsearch.node.overview.routeTitle', {
defaultMessage: 'Elasticsearch - Nodes - {nodeName} - Overview',
values: {
nodeName: data?.nodeSummary?.name,
},
});

const pageTitle = i18n.translate('xpack.monitoring.elasticsearch.node.overview.pageTitle', {
defaultMessage: 'Elasticsearch node: {node}',
values: {
node: data?.nodeSummary?.name,
},
});

const getPageData = useCallback(async () => {
const bounds = services.data?.query.timefilter.timefilter.getBounds();
const url = `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/nodes/${node}`;

const response = await services.http?.fetch(url, {
method: 'POST',
body: JSON.stringify({
showSystemIndices,
ccs,
timeRange: {
min: bounds.min.toISOString(),
max: bounds.max.toISOString(),
},
is_advanced: false,
}),
});

setData(response);
const transformer = nodesByIndices();
setNodesByIndicesData(transformer(response.shards, response.nodes));
}, [
ccs,
clusterUuid,
services.data?.query.timefilter.timefilter,
services.http,
node,
showSystemIndices,
]);

const toggleShowSystemIndices = useCallback(() => {
setShowSystemIndices(!showSystemIndices);
}, [showSystemIndices, setShowSystemIndices]);

return (
<ElasticsearchTemplate
title={title}
pageTitle={pageTitle}
getPageData={getPageData}
cluster={cluster}
>
<div data-test-subj="elasticsearchNodeListingPage">
<SetupModeRenderer
render={({ setupMode, flyoutComponent, bottomBarComponent }: SetupModeProps) => (
<SetupModeContext.Provider value={{ setupModeSupported: true }}>
{flyoutComponent}
<NodeReact
alerts={{}}
nodeId={node}
clusterUuid={clusterUuid}
onBrush={onBrush}
zoomInfo={zoomInfo}
toggleShowSystemIndices={toggleShowSystemIndices}
showSystemIndices={showSystemIndices}
nodesByIndices={nodesByIndicesData}
{...data}
/>
{bottomBarComponent}
</SetupModeContext.Provider>
)}
/>
</div>
</ElasticsearchTemplate>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@

export const ElasticsearchOverview: FunctionComponent<Props>;
export const ElasticsearchNodes: FunctionComponent<Props>;
export const NodeReact: FunctionComponent<Props>;
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@

export { ElasticsearchOverview } from './overview';
export { ElasticsearchNodes } from './nodes';
export { NodeReact } from './node';
export { ElasticsearchIndices } from './indices';
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
*/

export { NodeStatusIcon } from './status_icon';
export { Node } from './node';
export { NodeReact } from './node_react';
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import {
EuiPage,
EuiPageContent,
EuiPageBody,
EuiSpacer,
EuiFlexGrid,
EuiFlexItem,
EuiPanel,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { NodeDetailStatus } from '../node_detail_status';
import { Logs } from '../../logs';
import { MonitoringTimeseriesContainer } from '../../chart';
import { AlertsCallout } from '../../../alerts/callout';
import { ShardAllocationReact } from '../shard_allocation';

export const NodeReact = ({
nodeSummary,
metrics,
logs,
alerts,
nodeId,
clusterUuid,
...props
}) => {
/*
// This isn't doing anything due to a possible bug. https://github.com/elastic/kibana/issues/106309
if (alerts) {
for (const alertTypeId of Object.keys(alerts)) {
const alertInstance = alerts[alertTypeId];
for (const { meta } of alertInstance.states) {
const metricList = get(meta, 'metrics', []);
for (const metric of metricList) {
if (metrics[metric]) {
metrics[metric].alerts = metrics[metric].alerts || {};
metrics[metric].alerts[alertTypeId] = alertInstance;
}
}
}
}
}
*/
const metricsToShow = [
metrics.node_jvm_mem,
metrics.node_mem,
metrics.node_total_io,
metrics.node_cpu_metric,
metrics.node_load_average,
metrics.node_latency,
metrics.node_segment_count,
];

return (
<EuiPage>
<EuiPageBody>
<EuiScreenReaderOnly>
<h1>
<FormattedMessage
id="xpack.monitoring.elasticsearch.node.heading"
defaultMessage="Elasticsearch node"
/>
</h1>
</EuiScreenReaderOnly>
<EuiPanel>
<NodeDetailStatus stats={nodeSummary} alerts={alerts} />
</EuiPanel>
<EuiSpacer size="m" />
<AlertsCallout alerts={alerts} />
<EuiPageContent>
<EuiFlexGrid columns={2} gutterSize="s">
{metricsToShow.map((metric, index) => (
<EuiFlexItem key={index}>
<MonitoringTimeseriesContainer series={metric} {...props} />
<EuiSpacer />
</EuiFlexItem>
))}
</EuiFlexGrid>
</EuiPageContent>
<EuiSpacer size="m" />
<EuiPanel>
<Logs logs={logs} nodeId={nodeId} clusterUuid={clusterUuid} />
</EuiPanel>
<EuiSpacer size="m" />
<EuiPanel>
<ShardAllocationReact {...props} />
</EuiPanel>
</EuiPageBody>
</EuiPage>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { TableHeadReact } from './table_head_react';
import { TableBody } from './table_body';
import { labels } from '../lib/labels';

export const ClusterViewReact = (props) => {
return (
<table cellPadding="0" cellSpacing="0" className="table">
<TableHeadReact
labels={labels.node}
toggleShowSystemIndices={props.toggleShowSystemIndices}
showSystemIndices={props.showSystemIndices}
/>
<TableBody
filter={props.filter}
totalCount={props.totalCount}
rows={props.nodesByIndices}
cols={labels.node.length}
shardStats={props.shardStats}
/>
</table>
);
};
Loading

0 comments on commit 682a1c1

Please sign in to comment.