From ca5ca2c0f632c868e69a4c97ec4916e69d842225 Mon Sep 17 00:00:00 2001 From: Mohamed OULD HOCINE <106236152+gally47@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:28:33 +0800 Subject: [PATCH] added actions on datanode datails page (#17840) * added actions on datanode datails page * changelog file * use fetchDataNode instead of the list api call for details page * reuse DataNodeActions component * defaultProp displayAs: 'dropdown' --------- Co-authored-by: Ousmane SAMBA --- changelog/unreleased/issue-17705.toml | 5 ++ .../datanode/DataNodeList/DataNodeActions.tsx | 48 ++++++++++++----- .../components/datanode/hooks/useDataNode.ts | 53 +++++++++++++++++++ .../src/pages/DataNodePage.tsx | 30 +++++++---- 4 files changed, 111 insertions(+), 25 deletions(-) create mode 100644 changelog/unreleased/issue-17705.toml create mode 100644 graylog2-web-interface/src/components/datanode/hooks/useDataNode.ts diff --git a/changelog/unreleased/issue-17705.toml b/changelog/unreleased/issue-17705.toml new file mode 100644 index 000000000000..0659d08686db --- /dev/null +++ b/changelog/unreleased/issue-17705.toml @@ -0,0 +1,5 @@ +type = "fixed" +message = "added actions on datanode datails page" + +issues = ["17705"] +pulls = ["17840"] diff --git a/graylog2-web-interface/src/components/datanode/DataNodeList/DataNodeActions.tsx b/graylog2-web-interface/src/components/datanode/DataNodeList/DataNodeActions.tsx index a5e6e04de97e..160f5c82f5a2 100644 --- a/graylog2-web-interface/src/components/datanode/DataNodeList/DataNodeActions.tsx +++ b/graylog2-web-interface/src/components/datanode/DataNodeList/DataNodeActions.tsx @@ -16,12 +16,13 @@ */ import * as React from 'react'; import { useState } from 'react'; +import styled from 'styled-components'; -import type { DataNode } from 'preflight/types'; import { ConfirmDialog } from 'components/common'; -import { MenuItem } from 'components/bootstrap'; +import { Button, MenuItem } from 'components/bootstrap'; import OverlayDropdownButton from 'components/common/OverlayDropdownButton'; import { MORE_ACTIONS_TITLE, MORE_ACTIONS_HOVER_TITLE } from 'components/common/EntityDataTable/Constants'; +import type { DataNode } from 'preflight/types'; import { rejoinDataNode, @@ -31,8 +32,13 @@ import { startDataNode, } from '../hooks/useDataNodes'; +const ActionButton = styled(Button)` + margin-left: 4px; +`; + type Props = { dataNode: DataNode, + displayAs?: 'dropdown'|'buttons', }; const DIALOG_TYPES = { @@ -57,7 +63,7 @@ const DIALOG_TEXT = { }, }; -const DataNodeActions = ({ dataNode }: Props) => { +const DataNodeActions = ({ dataNode, displayAs }: Props) => { const [showDialog, setShowDialog] = useState(false); const [dialogType, setDialogType] = useState(null); @@ -120,17 +126,27 @@ const DataNodeActions = ({ dataNode }: Props) => { return ( <> - - renewDatanodeCertificate(dataNode.node_id)}>Renew certificate - {!isDatanodeRunning && startDataNode(dataNode.node_id)}>Start} - {isDatanodeRunning && handleAction(DIALOG_TYPES.STOP)}>Stop} - {isDatanodeRemoved && handleAction(DIALOG_TYPES.REJOIN)}>Rejoin} - {(!isDatanodeRemoved || isRemovingDatanode) && handleAction(DIALOG_TYPES.REMOVE)}>Remove} - + {displayAs === 'dropdown' && ( + + renewDatanodeCertificate(dataNode.node_id)}>Renew certificate + {!isDatanodeRunning && startDataNode(dataNode.node_id)}>Start} + {isDatanodeRunning && handleAction(DIALOG_TYPES.STOP)}>Stop} + {isDatanodeRemoved && handleAction(DIALOG_TYPES.REJOIN)}>Rejoin} + {(!isDatanodeRemoved || isRemovingDatanode) && handleAction(DIALOG_TYPES.REMOVE)}>Remove} + + )} + {displayAs === 'buttons' && ( + <> + {!isDatanodeRunning && startDataNode(dataNode.node_id)} bsSize="small">Start} + {isDatanodeRunning && handleAction(DIALOG_TYPES.STOP)} bsSize="small">Stop} + {isDatanodeRemoved && handleAction(DIALOG_TYPES.REJOIN)} bsSize="small">Rejoin} + {(!isDatanodeRemoved || isRemovingDatanode) && handleAction(DIALOG_TYPES.REMOVE)} bsSize="small">Remove} + + )} {showDialog && ( { ); }; +DataNodeActions.defaultProps = { + displayAs: 'dropdown', +}; + export default DataNodeActions; diff --git a/graylog2-web-interface/src/components/datanode/hooks/useDataNode.ts b/graylog2-web-interface/src/components/datanode/hooks/useDataNode.ts new file mode 100644 index 000000000000..b355c766b145 --- /dev/null +++ b/graylog2-web-interface/src/components/datanode/hooks/useDataNode.ts @@ -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 + * . + */ +import { useQuery } from '@tanstack/react-query'; + +import { qualifyUrl } from 'util/URLUtils'; +import type { DataNode } from 'preflight/types'; +import UserNotification from 'util/UserNotification'; +import fetch from 'logic/rest/FetchProvider'; + +const fetchDataNode = async (datanodeId: string) => fetch('GET', qualifyUrl(`/datanode/${datanodeId}`)); + +const useDataNode = (datanodeId: string) : { + data: DataNode, + refetch: () => void, + isInitialLoading: boolean, + error: any, +} => { + const { data, refetch, isInitialLoading, error } = useQuery( + ['datanode'], + () => fetchDataNode(datanodeId), + { + onError: (errorThrown) => { + UserNotification.error(`Loading Data Node failed with status: ${errorThrown}`, + 'Could not load Data Node'); + }, + notifyOnChangeProps: ['data', 'error'], + refetchInterval: 5000, + }, + ); + + return ({ + data, + refetch, + isInitialLoading, + error, + }); +}; + +export default useDataNode; diff --git a/graylog2-web-interface/src/pages/DataNodePage.tsx b/graylog2-web-interface/src/pages/DataNodePage.tsx index fb951c23d814..9435f92d69e2 100644 --- a/graylog2-web-interface/src/pages/DataNodePage.tsx +++ b/graylog2-web-interface/src/pages/DataNodePage.tsx @@ -17,15 +17,16 @@ import * as React from 'react'; import styled, { css } from 'styled-components'; +import type { DataNode } from 'preflight/types'; import useParams from 'routing/useParams'; -import useDataNodes from 'components/datanode/hooks/useDataNodes'; import DataNodesPageNavigation from 'components/datanode/DataNodePageNavigation'; import DocsHelper from 'util/DocsHelper'; import { Row, Col, Label } from 'components/bootstrap'; -import { DocumentTitle, PageHeader, RelativeTime, Spinner } from 'components/common'; -import type { SearchParams } from 'stores/PaginationTypes'; +import { DocumentTitle, NoSearchResult, PageHeader, RelativeTime, Spinner } from 'components/common'; import { CertRenewalButton } from 'components/datanode/DataNodeConfiguration/CertificateRenewal'; import Icon from 'components/common/Icon'; +import useDataNode from 'components/datanode/hooks/useDataNode'; +import DataNodeActions from 'components/datanode/DataNodeList/DataNodeActions'; const StyledHorizontalDl = styled.dl(({ theme }) => css` margin: ${theme.spacings.md} 0; @@ -57,19 +58,23 @@ const BooleanValue = ({ value }: { value: boolean }) => ( <> {value ? 'yes' : 'no'} ); +const ActionsCol = styled(Col)` + text-align: right; +`; + const DataNodePage = () => { const { dataNodeId } = useParams(); - const { data: { elements }, isInitialLoading } = useDataNodes({ - query: '', - page: 1, - pageSize: 0, - } as SearchParams); + const { data, isInitialLoading, error } = useDataNode(dataNodeId); if (isInitialLoading) { return ; } - const datanode = elements.find((node) => node.node_id === dataNodeId); + if (!isInitialLoading && (!data || error)) { + return Error: {error?.message}; + } + + const datanode = data as DataNode; const datanodeDisabled = datanode.data_node_status !== 'AVAILABLE'; return ( @@ -81,8 +86,8 @@ const DataNodePage = () => { path: DocsHelper.PAGES.GRAYLOG_DATA_NODE, }} /> - - + +

Details:

Hostname:
@@ -104,6 +109,9 @@ const DataNodePage = () => {
+ + +