mirror of
https://github.com/Graylog2/graylog2-server.git
synced 2026-03-13 09:32:21 +08:00
Data nodes overview for preflight UI (#15214)
* Implement basic data nodes overview. * Create foundation for CA configuration step. * Implement hook to fetch available data nodes. * Implement hook to fetch data nodes CA status. * Display different configuration steps based on data nodes status. * Fixing tests. * Cleanup code. * Improve styling of configuration steps overview. * Fix theme spacing. * Cleanup code. * Improve styling of steps overview. * Implement dropzone to upload CA. * Improve overall styling. * Unify text color. * Implement configuration step for certificate provisioning. * Implement configuration step for finished configuration. * Improve headline styling. * Fix naming. * Add test for `useConfigurationStep` hook. * Create components directory. * ADding test for data nodes overview. * Adding test for `DataNodesOverview`. * Adding test for `ConfigurationWizard`. * Improve usage of list items. * Fixing rebase * REmove dat nodes mocks and use actual API endpoint. * Configure dev server for preflight UI webpack setup, to be able to proxy API requests. * Update data nodes object structure. * Configure timezone provider. * Change structure of data nodes object. * Improve layout * Add missing timezone provider. * Remove configuration wizard for now. * Fix `useDataNodes` test. * Remove further not needed code. * Fix preflight status API routes. * Display data nodes overview as list instead of table. * Improve descriptions. * Implement documentation links. * Adding test * Remove not needed timezone provider. * Cleanup code. * Improve wording. * Add missing license header.
This commit is contained in:
@@ -32,7 +32,7 @@ import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
|
||||
@Path(PreflightConstants.API_PREFIX + "/status")
|
||||
@Path(PreflightConstants.API_PREFIX + "status")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public class PreflightStatusResource {
|
||||
|
||||
|
||||
@@ -214,12 +214,16 @@ function queuePromiseIfNotLoggedin<T>(promise: () => Promise<T>): () => Promise<
|
||||
|
||||
type Method = 'GET' | 'PUT' | 'POST' | 'DELETE';
|
||||
|
||||
export default function fetch<T = any>(method: Method, url: string, body?: any): Promise<T> {
|
||||
export default function fetch<T = any>(method: Method, url: string, body?: any, requireSession: boolean = true): Promise<T> {
|
||||
const promise = () => new Builder(method, url)
|
||||
.json(body)
|
||||
.build();
|
||||
|
||||
return queuePromiseIfNotLoggedin(promise)();
|
||||
if (requireSession) {
|
||||
return queuePromiseIfNotLoggedin(promise)();
|
||||
}
|
||||
|
||||
return promise();
|
||||
}
|
||||
|
||||
export function fetchPlainText(method, url, body) {
|
||||
|
||||
@@ -15,21 +15,42 @@
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import { AppShell } from '@mantine/core';
|
||||
import { AppShell, Title, Space } from '@mantine/core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Section from 'preflight/common/Section';
|
||||
import Button from 'preflight/common/Button';
|
||||
import Section from 'preflight/components/common/Section';
|
||||
import Navigation from 'preflight/navigation/Navigation';
|
||||
import DataNodesOverview from 'preflight/components/DataNodesOverview';
|
||||
|
||||
import DocumentationLink from '../components/support/DocumentationLink';
|
||||
|
||||
const P = styled.p`
|
||||
max-width: 700px;
|
||||
`;
|
||||
|
||||
const App = () => (
|
||||
<AppShell padding="md" header={<Navigation />}>
|
||||
<Section title="Welcome!">
|
||||
<p>
|
||||
It looks like you are starting Graylog for the first time.
|
||||
Through this wizard, you can configure and secure your data nodes.
|
||||
</p>
|
||||
<Button size="xs">Continue</Button>
|
||||
<Section title="Welcome!" titleOrder={1}>
|
||||
<P>
|
||||
It looks like you are starting Graylog for the first time and have not configured a data node.<br />
|
||||
Data nodes allow you to index and search through all the messages in your Graylog message database.
|
||||
</P>
|
||||
<P>
|
||||
You can either implement a <DocumentationLink page="" text="Graylog data node" /> (recommended) or you can configure an <DocumentationLink page="" text="Elasticsearch" /> or <DocumentationLink page="" text="OpenSearch" /> node manually.
|
||||
</P>
|
||||
|
||||
<Space h="md" />
|
||||
<Title order={2}>Graylog Data Nodes</Title>
|
||||
<DataNodesOverview />
|
||||
|
||||
<Space h="md" />
|
||||
<Title order={2}>Manual Data Node Configuration</Title>
|
||||
<P>
|
||||
If you want to configure an Elasticsearch or OpenSearch node manually, you need to adjust the Graylog configuration and restart the Graylog server.
|
||||
After the restart this page will not show up again.
|
||||
</P>
|
||||
</Section>
|
||||
</AppShell>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Graylog, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import { render, screen, waitFor } from 'wrappedTestingLibrary';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import fetch from 'logic/rest/FetchProvider';
|
||||
import DataNodesOverview from 'preflight/components/DataNodesOverview';
|
||||
import useDataNodes from 'preflight/hooks/useDataNodes';
|
||||
import { asMock } from 'helpers/mocking';
|
||||
|
||||
jest.mock('preflight/hooks/useDataNodes');
|
||||
jest.mock('logic/rest/FetchProvider', () => jest.fn(() => Promise.resolve()));
|
||||
|
||||
const availableDataNodes = [
|
||||
{
|
||||
hostname: '192.168.0.10',
|
||||
id: 'data-node-id-1',
|
||||
is_leader: false,
|
||||
is_master: false,
|
||||
last_seen: '2020-01-10T10:40:00.000Z',
|
||||
node_id: 'node-id-complete-1',
|
||||
short_node_id: 'node-id-1',
|
||||
transport_address: 'http://localhost:9200',
|
||||
type: 'DATANODE',
|
||||
},
|
||||
{
|
||||
hostname: '192.168.0.11',
|
||||
id: 'data-node-id-2',
|
||||
is_leader: false,
|
||||
is_master: false,
|
||||
last_seen: '2020-01-10T10:40:00.000Z',
|
||||
node_id: 'node-id-complete-2',
|
||||
short_node_id: 'node-id-2',
|
||||
transport_address: 'http://localhost:9201',
|
||||
type: 'DATANODE',
|
||||
},
|
||||
{
|
||||
hostname: '192.168.0.12',
|
||||
id: 'data-node-id-3',
|
||||
is_leader: false,
|
||||
is_master: false,
|
||||
last_seen: '2020-01-10T10:40:00.000Z',
|
||||
node_id: 'node-id-complete-3',
|
||||
short_node_id: 'node-id-3',
|
||||
transport_address: 'http://localhost:9202',
|
||||
type: 'DATANODE',
|
||||
},
|
||||
];
|
||||
|
||||
describe('DataNodesOverview', () => {
|
||||
beforeEach(() => {
|
||||
asMock(useDataNodes).mockReturnValue({
|
||||
data: availableDataNodes,
|
||||
isFetching: false,
|
||||
isInitialLoading: false,
|
||||
error: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should list available data nodes', async () => {
|
||||
render(<DataNodesOverview />);
|
||||
|
||||
await screen.findByText('node-id-3');
|
||||
await screen.findByText('http://localhost:9200');
|
||||
});
|
||||
|
||||
it('should resume startup', async () => {
|
||||
render(<DataNodesOverview />);
|
||||
|
||||
await screen.findByText('node-id-3');
|
||||
|
||||
const resumeStartupButton = screen.getByRole('button', {
|
||||
name: /resume startup/i,
|
||||
});
|
||||
|
||||
userEvent.click(resumeStartupButton);
|
||||
|
||||
await waitFor(() => expect(fetch).toHaveBeenCalledWith('POST', expect.stringContaining('/api/status/finish-config'), undefined, false));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Graylog, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import { Space } from '@mantine/core';
|
||||
import styled from 'styled-components';
|
||||
import { useState } from 'react';
|
||||
|
||||
import UserNotification from 'util/UserNotification';
|
||||
import Spinner from 'components/common/Spinner';
|
||||
import useDataNodes from 'preflight/hooks/useDataNodes';
|
||||
import { Alert, Badge, List, Button } from 'preflight/components/common';
|
||||
import fetch from 'logic/rest/FetchProvider';
|
||||
import { qualifyUrl } from 'util/URLUtils';
|
||||
|
||||
const P = styled.p`
|
||||
max-width: 700px;
|
||||
`;
|
||||
|
||||
const NodeId = styled(Badge)`
|
||||
margin-right: 3px;
|
||||
`;
|
||||
|
||||
const DataNodesOverview = () => {
|
||||
const [resumingStartup, setResumingStartup] = useState(false);
|
||||
const {
|
||||
data: dataNodes,
|
||||
isFetching: isFetchingDataNodes,
|
||||
error: dataNodesFetchError,
|
||||
isInitialLoading: isInitialLoadingDataNodes,
|
||||
} = useDataNodes();
|
||||
|
||||
const resumeStartup = () => (
|
||||
fetch('POST', qualifyUrl('/api/status/finish-config'), undefined, false)
|
||||
.then(() => {
|
||||
setResumingStartup(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
setResumingStartup(false);
|
||||
|
||||
UserNotification.error(`Resuming startup failed with: ${error}`,
|
||||
'Could not resume startup');
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<P>
|
||||
Graylog data nodes offer a better integration with Graylog and simplify future updates.
|
||||
Once a Graylog data node is running, you can click on "Resume startup".
|
||||
</P>
|
||||
<P>
|
||||
These are the data nodes which are currently registered.
|
||||
The list is constantly updated. {isFetchingDataNodes && <Spinner text="" />}
|
||||
</P>
|
||||
|
||||
{!!dataNodes.length && (
|
||||
<>
|
||||
<Space h="sm" />
|
||||
<List spacing="xs">
|
||||
{dataNodes.map(({
|
||||
hostname,
|
||||
transport_address,
|
||||
short_node_id,
|
||||
}) => (
|
||||
<List.Item key={short_node_id}>
|
||||
<NodeId title="Short node id">{short_node_id}</NodeId>
|
||||
<span title="Transport address">{transport_address}</span>{' – '}
|
||||
<span title="Hostname">{hostname}</span>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
</>
|
||||
)}
|
||||
{(!dataNodes.length && !isInitialLoadingDataNodes) && (
|
||||
<Alert type="info">
|
||||
No data nodes have been found.
|
||||
</Alert>
|
||||
)}
|
||||
{dataNodesFetchError && (
|
||||
<Alert type="danger">
|
||||
There was an error fetching the data nodes: {dataNodesFetchError.message}
|
||||
</Alert>
|
||||
)}
|
||||
<Space h="md" />
|
||||
<Button onClick={resumeStartup} disabled={!dataNodes.length || resumingStartup} size="xs">
|
||||
{resumingStartup ? <Spinner delay={0} text="Resuming startup..." /> : 'Resume startup'}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataNodesOverview;
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Graylog, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import styled, { css, ThemeContext } from 'styled-components';
|
||||
import { Alert as MantineAlert } from '@mantine/core';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { ColorVariants } from '../../theme/types';
|
||||
|
||||
const StyledAlert = styled(MantineAlert)(({ theme }) => css`
|
||||
margin: ${theme.spacings.md} 0;
|
||||
`);
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode,
|
||||
type: ColorVariants,
|
||||
};
|
||||
|
||||
const Alert = ({ children, type }: Props) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
|
||||
const alertStyles = () => ({
|
||||
root: {
|
||||
borderColor: theme.colors.variant.lighter[type],
|
||||
backgroundColor: `${theme.colors.variant.lightest[type]} !important`,
|
||||
},
|
||||
message: {
|
||||
fontSize: theme.fonts.size.medium.rem,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledAlert styles={alertStyles} color={theme.colors.variant.lightest[type]}>
|
||||
{children}
|
||||
</StyledAlert>
|
||||
);
|
||||
};
|
||||
|
||||
export default Alert;
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Graylog, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import type { BadgeProps } from '@mantine/core';
|
||||
import { Badge as MantineBadge } from '@mantine/core';
|
||||
|
||||
type Props = BadgeProps & {
|
||||
title: string,
|
||||
}
|
||||
|
||||
const Badge = ({ children, ...props }: Props) => (
|
||||
<MantineBadge {...props}>
|
||||
{children}
|
||||
</MantineBadge>
|
||||
);
|
||||
export default Badge;
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Graylog, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
import type { ListProps } from '@mantine/core';
|
||||
import { List as MantineList } from '@mantine/core';
|
||||
|
||||
const StyledList = styled(MantineList)(({ theme }) => css`
|
||||
color: ${theme.colors.global.textDefault};
|
||||
`);
|
||||
|
||||
type ListComponent = ((props: ListProps) => React.ReactElement) & {
|
||||
Item: typeof MantineList.Item
|
||||
}
|
||||
|
||||
const List: ListComponent = ({ children, ...props }: ListProps) => (
|
||||
<StyledList {...props}>
|
||||
{children}
|
||||
</StyledList>
|
||||
);
|
||||
|
||||
List.Item = MantineList.Item;
|
||||
export default List;
|
||||
@@ -18,10 +18,10 @@ import * as React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
import type { DefaultTheme } from 'styled-components';
|
||||
import { Box, Title } from '@mantine/core';
|
||||
import type { BoxProps } from '@mantine/core';
|
||||
import type { BoxProps, TitleOrder } from '@mantine/core';
|
||||
|
||||
import Col from 'preflight/common/Col';
|
||||
import Row from 'preflight/common/Row';
|
||||
import Col from 'preflight/components/common/Col';
|
||||
import Row from 'preflight/components/common/Row';
|
||||
|
||||
type ContainerType = BoxProps & {
|
||||
theme: DefaultTheme,
|
||||
@@ -42,23 +42,19 @@ const SectionContainer = styled(SubsectionContainer)(({ theme }: ContainerType)
|
||||
background-color: ${theme.colors.global.contentBackground};
|
||||
border: 1px solid ${theme.colors.variant.lighter.default};
|
||||
border-radius: 4px;
|
||||
min-height: 80vh;
|
||||
`);
|
||||
|
||||
const SectionTitle = styled(Title)(({ theme }) => css`
|
||||
margin-bottom: ${theme.spacings.md};
|
||||
`);
|
||||
|
||||
type Props = {
|
||||
title: React.ReactNode,
|
||||
actions?: React.ReactNode,
|
||||
titleOrder?: TitleOrder
|
||||
};
|
||||
|
||||
const SectionHeader = ({ title, actions }: Props) => {
|
||||
const SectionHeader = ({ title, actions, titleOrder }: Props) => {
|
||||
return (
|
||||
<Row>
|
||||
<Col lg={6} md={6}>
|
||||
<SectionTitle order={2}>{title}</SectionTitle>
|
||||
<Title order={titleOrder}>{title}</Title>
|
||||
</Col>
|
||||
<Col lg={6} md={6}>
|
||||
<TitleActionContainer>{actions}</TitleActionContainer>
|
||||
@@ -69,12 +65,13 @@ const SectionHeader = ({ title, actions }: Props) => {
|
||||
|
||||
SectionHeader.defaultProps = {
|
||||
actions: undefined,
|
||||
titleOrder: 2,
|
||||
};
|
||||
|
||||
export const Subsection = ({ title, children, actions }: React.PropsWithChildren<Props>) => {
|
||||
export const Subsection = ({ title, children, actions, titleOrder }: React.PropsWithChildren<Props>) => {
|
||||
return (
|
||||
<SubsectionContainer component="section">
|
||||
<SectionHeader title={title} actions={actions} />
|
||||
<SectionHeader title={title} actions={actions} titleOrder={titleOrder} />
|
||||
{children}
|
||||
</SubsectionContainer>
|
||||
);
|
||||
@@ -82,12 +79,13 @@ export const Subsection = ({ title, children, actions }: React.PropsWithChildren
|
||||
|
||||
Subsection.defaultProps = {
|
||||
actions: undefined,
|
||||
titleOrder: undefined,
|
||||
};
|
||||
|
||||
const Section = ({ title, children, actions }: React.PropsWithChildren<Props>) => {
|
||||
const Section = ({ title, children, actions, titleOrder }: React.PropsWithChildren<Props>) => {
|
||||
return (
|
||||
<SectionContainer component="section">
|
||||
<SectionHeader title={title} actions={actions} />
|
||||
<SectionHeader title={title} actions={actions} titleOrder={titleOrder} />
|
||||
{children}
|
||||
</SectionContainer>
|
||||
);
|
||||
@@ -95,6 +93,7 @@ const Section = ({ title, children, actions }: React.PropsWithChildren<Props>) =
|
||||
|
||||
Section.defaultProps = {
|
||||
actions: undefined,
|
||||
titleOrder: undefined,
|
||||
};
|
||||
|
||||
export default Section;
|
||||
@@ -14,6 +14,8 @@
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
export { default as Alert } from './Alert';
|
||||
export { default as Badge } from './Badge';
|
||||
export { default as Button } from './Button';
|
||||
export { default as Col } from './Col';
|
||||
export { default as Icon } from './Icon';
|
||||
@@ -21,6 +23,7 @@ export { default as Menu } from './Menu';
|
||||
export { default as MenuItem } from './MenuItem';
|
||||
export { default as Row } from './Row';
|
||||
export { default as Section } from './Section';
|
||||
export { default as List } from './List';
|
||||
export { default as MenuTarget } from './mantine/MenuTarget';
|
||||
export { default as MenuDropdownWrapper } from './mantine/MenuDropdownWrapper';
|
||||
export * from './mantine/imports';
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Graylog, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
import { renderHook } from 'wrappedTestingLibrary/hooks';
|
||||
|
||||
import asMock from 'helpers/mocking/AsMock';
|
||||
import fetch from 'logic/rest/FetchProvider';
|
||||
|
||||
import useDataNodes from './useDataNodes';
|
||||
|
||||
jest.mock('logic/rest/FetchProvider', () => jest.fn());
|
||||
|
||||
describe('useDataNodes', () => {
|
||||
const availableDataNodes = [
|
||||
{
|
||||
id: 'data-node-id-1',
|
||||
name: 'data-node-name',
|
||||
transportAddress: 'transport.address1',
|
||||
altNames: [],
|
||||
status: 'UNCONFIGURED',
|
||||
},
|
||||
{
|
||||
id: 'data-node-id-2',
|
||||
name: 'data-node-name',
|
||||
altNames: [],
|
||||
transportAddress: 'transport.address2',
|
||||
status: 'UNCONFIGURED',
|
||||
},
|
||||
{
|
||||
id: 'data-node-id-3',
|
||||
name: 'data-node-name',
|
||||
altNames: [],
|
||||
transportAddress: 'transport.address3',
|
||||
status: 'UNCONFIGURED',
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
asMock(fetch).mockReturnValue(Promise.resolve(availableDataNodes));
|
||||
});
|
||||
|
||||
it('should return data nodes CA status', async () => {
|
||||
const { result, waitFor } = renderHook(() => useDataNodes());
|
||||
|
||||
expect(result.current.data).toEqual([]);
|
||||
|
||||
await waitFor(() => result.current.isFetching);
|
||||
await waitFor(() => !result.current.isFetching);
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith('GET', expect.stringContaining('/api/data_nodes'), undefined, false);
|
||||
|
||||
await waitFor(() => expect(result.current.data).toEqual(availableDataNodes));
|
||||
});
|
||||
});
|
||||
51
graylog2-web-interface/src/preflight/hooks/useDataNodes.ts
Normal file
51
graylog2-web-interface/src/preflight/hooks/useDataNodes.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Graylog, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { qualifyUrl } from 'util/URLUtils';
|
||||
import fetch from 'logic/rest/FetchProvider';
|
||||
import type { DataNodes } from 'preflight/types';
|
||||
import type FetchError from 'logic/errors/FetchError';
|
||||
|
||||
const fetchDataNodes = () => (
|
||||
fetch('GET', qualifyUrl('/api/data_nodes'), undefined, false)
|
||||
);
|
||||
|
||||
const useDataNodes = (): {
|
||||
data: DataNodes,
|
||||
isFetching: boolean,
|
||||
isInitialLoading: boolean,
|
||||
error: FetchError
|
||||
} => {
|
||||
const {
|
||||
data,
|
||||
isFetching,
|
||||
error,
|
||||
isInitialLoading,
|
||||
} = useQuery<DataNodes, FetchError>(
|
||||
['data-nodes', 'overview'],
|
||||
fetchDataNodes,
|
||||
{
|
||||
initialData: [],
|
||||
refetchInterval: 3000,
|
||||
});
|
||||
|
||||
return { data, isFetching, isInitialLoading, error };
|
||||
};
|
||||
|
||||
export default useDataNodes;
|
||||
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { Button, Icon, Menu, MenuTarget, MenuItem, MenuDropdownWrapper, Text } from 'preflight/common';
|
||||
import { Button, Icon, Menu, MenuTarget, MenuItem, MenuDropdownWrapper, Text } from 'preflight/components/common';
|
||||
|
||||
const StyledButton = styled(Button)(({ theme }) => css`
|
||||
border-radius: 50px;
|
||||
|
||||
@@ -20,7 +20,7 @@ import type { DefaultTheme } from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import HelpMenu from 'preflight/navigation/HelpMenu';
|
||||
import { Group, Header, Text } from 'preflight/common';
|
||||
import { Group, Header, Text } from 'preflight/components/common';
|
||||
|
||||
import NavigationBrand from './NavigationBrand';
|
||||
import ThemeModeToggle from './ThemeModeToggle';
|
||||
|
||||
@@ -22,7 +22,7 @@ import styled, { css, withTheme } from 'styled-components';
|
||||
import defer from 'lodash/defer';
|
||||
|
||||
import ThemePropTypes from 'preflight/theme/types';
|
||||
import { Icon } from 'preflight/common';
|
||||
import { Icon } from 'preflight/components/common';
|
||||
import {
|
||||
THEME_MODE_LIGHT,
|
||||
THEME_MODE_DARK,
|
||||
|
||||
@@ -61,6 +61,7 @@ const ThemeWrapper = ({ children }: Props) => {
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
xxs: theme.spacings.xxs,
|
||||
xs: theme.spacings.xs,
|
||||
sm: theme.spacings.sm,
|
||||
md: theme.spacings.md,
|
||||
|
||||
30
graylog2-web-interface/src/preflight/types.ts
Normal file
30
graylog2-web-interface/src/preflight/types.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Graylog, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
|
||||
export type DataNode = {
|
||||
hostname: string,
|
||||
id: string,
|
||||
is_leader: boolean,
|
||||
is_master: boolean,
|
||||
last_seen: string,
|
||||
node_id: string,
|
||||
short_node_id: string,
|
||||
transport_address: string,
|
||||
type: string,
|
||||
}
|
||||
|
||||
export type DataNodes = Array<DataNode>;
|
||||
@@ -22,6 +22,7 @@ const merge = require('webpack-merge');
|
||||
const { EsbuildPlugin } = require('esbuild-loader');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
|
||||
const { DEFAULT_API_URL } = require('./webpack.vendor');
|
||||
const supportedBrowsers = require('./supportedBrowsers');
|
||||
const core = require('./webpack/core');
|
||||
|
||||
@@ -33,6 +34,8 @@ const TARGET = process.env.npm_lifecycle_event || 'build';
|
||||
process.env.BABEL_ENV = TARGET;
|
||||
const mode = TARGET.startsWith('build') ? 'production' : 'development';
|
||||
|
||||
const apiUrl = process.env.GRAYLOG_API_URL ?? DEFAULT_API_URL;
|
||||
|
||||
const baseConfig = {
|
||||
mode,
|
||||
name: 'preflight',
|
||||
@@ -67,6 +70,17 @@ let webpackConfig;
|
||||
|
||||
if (mode === 'development') {
|
||||
webpackConfig = merge(baseConfig, {
|
||||
devServer: {
|
||||
hot: false,
|
||||
liveReload: true,
|
||||
compress: true,
|
||||
historyApiFallback: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: apiUrl,
|
||||
},
|
||||
},
|
||||
},
|
||||
devtool: 'cheap-module-source-map',
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
|
||||
@@ -31,7 +31,7 @@ const supportedBrowsers = require('./supportedBrowsers');
|
||||
const TARGET = process.env.npm_lifecycle_event || 'build';
|
||||
process.env.BABEL_ENV = TARGET;
|
||||
|
||||
const DEFAULT_API_URL = 'http://localhost:9000';
|
||||
export const DEFAULT_API_URL = 'http://localhost:9000';
|
||||
const apiUrl = process.env.GRAYLOG_API_URL ?? DEFAULT_API_URL;
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
Reference in New Issue
Block a user