From 219dcee5003412bb41e3263d8629727ea4c0c644 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Mon, 29 Jul 2024 13:43:55 +0300 Subject: [PATCH] feat(PDiskPage): add force restart --- .../ButtonWithConfirmDialog.tsx | 19 ++++++++++--- .../CriticalActionDialog.scss | 2 ++ .../CriticalActionDialog.tsx | 27 ++++++++++++------- .../CriticalActionDialog/i18n/en.json | 1 + src/containers/PDiskPage/PDiskPage.tsx | 7 ++--- src/containers/PDiskPage/i18n/en.json | 1 + src/services/api.ts | 24 ++++++++--------- src/types/api/restartPDisk.ts | 3 ++- 8 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.tsx b/src/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.tsx index 44e3d855a..55a75a4e1 100644 --- a/src/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.tsx +++ b/src/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.tsx @@ -7,9 +7,10 @@ import {CriticalActionDialog} from '../CriticalActionDialog'; interface ButtonWithConfirmDialogProps { children: React.ReactNode; - onConfirmAction: () => Promise; + onConfirmAction: (isRetry?: boolean) => Promise; onConfirmActionSuccess?: (() => Promise) | VoidFunction; dialogContent: string; + retryButtonText?: string; buttonDisabled?: ButtonProps['disabled']; buttonView?: ButtonProps['view']; buttonClassName?: ButtonProps['className']; @@ -24,6 +25,7 @@ export function ButtonWithConfirmDialog({ onConfirmAction, onConfirmActionSuccess, dialogContent, + retryButtonText, buttonDisabled = false, buttonView = 'action', buttonClassName, @@ -34,14 +36,17 @@ export function ButtonWithConfirmDialog({ }: ButtonWithConfirmDialogProps) { const [isConfirmDialogVisible, setIsConfirmDialogVisible] = React.useState(false); const [buttonLoading, setButtonLoading] = React.useState(false); + const [withRetry, setWithRetry] = React.useState(false); - const handleConfirmAction = async () => { + const handleConfirmAction = async (isRetry?: boolean) => { setButtonLoading(true); - await onConfirmAction(); + await onConfirmAction(isRetry); setButtonLoading(false); }; const handleConfirmActionSuccess = async () => { + setWithRetry(false); + if (onConfirmActionSuccess) { setButtonLoading(true); @@ -54,7 +59,11 @@ export function ButtonWithConfirmDialog({ } }; - const handleConfirmActionError = () => { + const handleConfirmActionError = (error: unknown) => { + const isWithRetry = Boolean( + error && typeof error === 'object' && 'retryPossible' in error && error.retryPossible, + ); + setWithRetry(isWithRetry); setButtonLoading(false); }; @@ -93,6 +102,8 @@ export function ButtonWithConfirmDialog({ { interface CriticalActionDialogProps { visible: boolean; text: string; + withRetry?: boolean; + retryButtonText?: string; onClose: VoidFunction; - onConfirm: () => Promise; + onConfirm: (isRetry?: boolean) => Promise; onConfirmActionSuccess: VoidFunction; - onConfirmActionError: VoidFunction; + onConfirmActionError: (error: unknown) => void; } export function CriticalActionDialog({ visible, text, + withRetry, + retryButtonText, onClose, onConfirm, onConfirmActionSuccess, @@ -43,17 +47,16 @@ export function CriticalActionDialog({ const [isLoading, setIsLoading] = React.useState(false); const [error, setError] = React.useState(); - const onSubmit = async (e: React.FormEvent) => { - e.preventDefault(); + const onApply = async (isRetry?: boolean) => { setIsLoading(true); - return onConfirm() + return onConfirm(isRetry) .then(() => { onConfirmActionSuccess(); onClose(); }) .catch((err) => { - onConfirmActionError(); + onConfirmActionError(err); setError(err); }) .finally(() => { @@ -75,7 +78,13 @@ export function CriticalActionDialog({ onApply(true)} onClickButtonCancel={onClose} /> @@ -83,7 +92,7 @@ export function CriticalActionDialog({ } return ( -
+ @@ -98,9 +107,9 @@ export function CriticalActionDialog({ textButtonCancel={criticalActionDialogKeyset('button-cancel')} propsButtonApply={{type: 'submit'}} onClickButtonCancel={onClose} - onClickButtonApply={() => {}} + onClickButtonApply={() => onApply()} /> - +
); }; diff --git a/src/components/CriticalActionDialog/i18n/en.json b/src/components/CriticalActionDialog/i18n/en.json index 424202683..9ee11a8a1 100644 --- a/src/components/CriticalActionDialog/i18n/en.json +++ b/src/components/CriticalActionDialog/i18n/en.json @@ -3,6 +3,7 @@ "no-rights-error": "You don't have enough rights to complete the operation", "button-confirm": "Confirm", + "button-retry": "Retry", "button-cancel": "Cancel", "button-close": "Close" } diff --git a/src/containers/PDiskPage/PDiskPage.tsx b/src/containers/PDiskPage/PDiskPage.tsx index 06e5ae46f..2c4dbb43e 100644 --- a/src/containers/PDiskPage/PDiskPage.tsx +++ b/src/containers/PDiskPage/PDiskPage.tsx @@ -79,11 +79,11 @@ export function PDiskPage() { const pDiskData = pdiskDataQuery.currentData; const {NodeHost, NodeId, NodeType, NodeDC, Severity} = pDiskData || {}; - const handleRestart = async () => { + const handleRestart = async (isRetry?: boolean) => { if (pDiskParamsDefined) { - return window.api.restartPDisk(nodeId, pDiskId).then((res) => { + return window.api.restartPDisk({nodeId, pDiskId, force: isRetry}).then((res) => { if (res?.result === false) { - const err = {statusText: res.error}; + const err = {statusText: res.error, retryPossible: res.forceRetryPossible}; throw err; } }); @@ -147,6 +147,7 @@ export function PDiskPage() { buttonDisabled={!nodeId || !pDiskId || !isUserAllowedToMakeChanges} buttonView="normal" dialogContent={pDiskPageKeyset('restart-pdisk-dialog')} + retryButtonText={pDiskPageKeyset('force-restart-pdisk-button')} withPopover popoverContent={pDiskPageKeyset('restart-pdisk-not-allowed')} popoverDisabled={isUserAllowedToMakeChanges} diff --git a/src/containers/PDiskPage/i18n/en.json b/src/containers/PDiskPage/i18n/en.json index ba8487309..05f5c2415 100644 --- a/src/containers/PDiskPage/i18n/en.json +++ b/src/containers/PDiskPage/i18n/en.json @@ -16,6 +16,7 @@ "no-slots-data": "No slots data", "restart-pdisk-button": "Restart PDisk", + "force-restart-pdisk-button": "Restart anyway", "restart-pdisk-dialog": "PDisk will be restarted. Do you want to proceed?", "restart-pdisk-not-allowed": "You don't have enough rights to restart PDisk" } diff --git a/src/services/api.ts b/src/services/api.ts index ebbba193d..86a394515 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -35,7 +35,6 @@ import type {TEvVDiskStateResponse} from '../types/api/vdisk'; import type {TUserToken} from '../types/api/whoami'; import type {QuerySyntax} from '../types/store/query'; import {BINARY_DATA_IN_PLAIN_TEXT_DISPLAY} from '../utils/constants'; -import {createPDiskDeveloperUILink} from '../utils/developerUI/developerUI'; import {prepareSortValue} from '../utils/filters'; import type {Nullable} from '../utils/typecheckers'; @@ -535,21 +534,20 @@ export class YdbEmbeddedAPI extends AxiosWrapper { }, ); } - restartPDisk(nodeId: number | string, pDiskId: number | string) { - const pDiskPath = createPDiskDeveloperUILink({ - nodeId, - pDiskId, - host: this.getPath(''), - }); - + restartPDisk({ + nodeId, + pDiskId, + force, + }: { + nodeId: number | string; + pDiskId: number | string; + force?: boolean; + }) { return this.post( - pDiskPath, - 'restartPDisk=', + this.getPath(`/pdisk/restart?node_id=${nodeId}&pdisk_id=${pDiskId}&force=${force}`), + {}, {}, { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - }, requestConfig: {'axios-retry': {retries: 0}}, }, ); diff --git a/src/types/api/restartPDisk.ts b/src/types/api/restartPDisk.ts index ded9518d7..98354f37e 100644 --- a/src/types/api/restartPDisk.ts +++ b/src/types/api/restartPDisk.ts @@ -1,6 +1,7 @@ export interface RestartPDiskResponse { // true if successful, false if not result?: boolean; - // Error message, example: "GroupId# 2181038081 ExpectedStatus# DISINTEGRATED" + // Error message error?: string; + forceRetryPossible?: boolean; }