diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/components/modals.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/components/modals.tsx index f21e54a44..3357844d0 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/components/modals.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/components/modals.tsx @@ -4,15 +4,58 @@ import { useCallback } from "react"; import { useModal } from "components/Modal/Modal"; +interface IUseParallelQueriesModalProps { + useSuccessBtn?: boolean; + cb?: (e) => void; + title?: VNode; + content?: VNode; + btnTxt?: VNode; +} + +const useParallelQueriesModal = ({ + useSuccessBtn, + cb, + title, + content, + btnTxt = Schedule, +}: IUseParallelQueriesModalProps) => { + const { toggleModal, setModalState } = useModal(); + const runAndClose = useCallback(() => { + cb(null); + toggleModal(); + }, [cb, toggleModal]); + + const showModal = useCallback(() => { + setModalState({ + content, + title, + successCb: useSuccessBtn ? runAndClose : undefined, + deleteCb: !useSuccessBtn ? runAndClose : undefined, + successBtnText: btnTxt, + deleteBtnText: btnTxt, + }); + toggleModal(); + }, [ + setModalState, + content, + title, + useSuccessBtn, + runAndClose, + btnTxt, + toggleModal, + ]); + return { showModal, toggleModal }; +}; + export const useScheduleUpgradeModal = ({ - allNodesReady, + useSuccessBtn, cb, }: IUseParallelQueriesModalProps) => { let title = All nodes are ready; let content = ( Schedule a firmware upgrade for all nodes on the network ); - if (!allNodesReady) { + if (!useSuccessBtn) { title = Some nodes are not ready; content = ( @@ -23,7 +66,7 @@ export const useScheduleUpgradeModal = ({ } return useParallelQueriesModal({ - allNodesReady, + useSuccessBtn, cb, title, content, @@ -31,14 +74,14 @@ export const useScheduleUpgradeModal = ({ }; export const useConfirmModal = ({ - allNodesReady, + useSuccessBtn, cb, }: IUseParallelQueriesModalProps) => { let title = All nodes are upgraded successfully; let content = ( Confirm mesh wide upgrade for all nodes on the network ); - if (!allNodesReady) { + if (!useSuccessBtn) { title = Some nodes don't upgraded properly; content = ( @@ -48,49 +91,27 @@ export const useConfirmModal = ({ ); } return useParallelQueriesModal({ - allNodesReady, + useSuccessBtn, cb, title, content, }); }; -interface IUseParallelQueriesModalProps { - allNodesReady: boolean; - cb?: (e) => void; - title?: VNode; - content?: VNode; -} - -const useParallelQueriesModal = ({ - allNodesReady, - cb, - title, - content, -}: IUseParallelQueriesModalProps) => { - const { toggleModal, setModalState } = useModal(); - const runAndClose = useCallback(() => { - cb(null); - toggleModal(); - }, [cb, toggleModal]); - - const showModal = useCallback(() => { - setModalState({ - content, - title, - successCb: allNodesReady ? runAndClose : undefined, - deleteCb: !allNodesReady ? runAndClose : undefined, - successBtnText: Schedule, - deleteBtnText: Schedule, - }); - toggleModal(); - }, [ - setModalState, - content, +export const useAbortModal = ({ cb }: IUseParallelQueriesModalProps) => { + const title = Abort current mesh wide upgrade?; + const content = ( + + This will the abort current upgrade process on all nodes. Are you + sure you want to proceed? + + ); + const btnTxt = Abort; + return useParallelQueriesModal({ + useSuccessBtn: false, + cb, title, - allNodesReady, - runAndClose, - toggleModal, - ]); - return { showModal, toggleModal }; + content, + btnTxt, + }); }; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/components/nodeUpgradeInfo.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/components/nodeUpgradeInfo.tsx index 775754693..644909dea 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/components/nodeUpgradeInfo.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/components/nodeUpgradeInfo.tsx @@ -10,6 +10,7 @@ import { detailedInfoStatusMessageMap, mainNodeStatusMessageMap, } from "plugins/lime-plugin-mesh-wide-upgrade/src/utils/upgradeStatusMessages"; +import UpdateNodeInfoBtn from "plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/UpdateNodeInfoBtn"; export interface INodeInfoBodyItemProps { title: ComponentChildren; @@ -49,7 +50,13 @@ const NodeUpgradeInfoItem = ({ title={name} description={descriptionMsg} leftComponent={} - rightText={"Info"} + rightText={ + + } > {mainNodeStatusInfo && } diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ConfirmationPending.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ConfirmationPending.tsx index 2bc4e62c7..6e10cb934 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ConfirmationPending.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ConfirmationPending.tsx @@ -1,25 +1,32 @@ import { Trans } from "@lingui/macro"; -import { ParallelErrors } from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ParallelErrors"; +import { + ParallelErrors, + UpgradeState, +} from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeState"; import { useParallelConfirmUpgrade } from "plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeQueries"; export const ConfirmationPending = () => { const { errors } = useParallelConfirmUpgrade(); + const title = ( + + Upgraded! +
+ Awaiting confirmation +
+ ); return ( - <> -
- Upgrade done -
-
+ + <> Check if network is working properly and confirm the upgrade
If not confirmed, the upgrade will be rolled back after a while
-
- {errors?.length > 0 && } - + {errors?.length > 0 && } + + ); }; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/Confirmed.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/Confirmed.tsx new file mode 100644 index 000000000..a8e22df13 --- /dev/null +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/Confirmed.tsx @@ -0,0 +1,29 @@ +import { Trans } from "@lingui/macro"; + +import { + MeshUpgradeErrorIcon, + MeshUpgradeSuccessIcon, + ParallelErrors, + UpgradeState, +} from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeState"; +import { useParallelConfirmUpgrade } from "plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeQueries"; + +export const Confirmed = () => { + const { errors } = useParallelConfirmUpgrade(); + // let icon =
; + let icon = ; + let title = Confirmed!; + let desc = Mesh upgrade confirmed successfully; + if (errors?.length > 0) { + icon = ; + title = Confirmed with some errors; + desc = Mesh upgrade confirmed with some errors; + } + + return ( + + {desc} + {errors?.length > 0 && } + + ); +}; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ErrorState.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ErrorState.tsx index 186be2212..bb8391712 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ErrorState.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ErrorState.tsx @@ -1,16 +1,18 @@ import { Trans } from "@lingui/macro"; import { VNode } from "preact"; +import { + MeshUpgradeErrorIcon, + UpgradeState, +} from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeState"; + export const ErrorState = ({ msg }: { msg: string | VNode }) => { return ( -
-
- ! -
-
- Error! -
-
{msg}
-
+ Error!
} + icon={} + > + {msg} + ); }; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/LoadingPage.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/LoadingPage.tsx index d46b30670..c17078fe4 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/LoadingPage.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/LoadingPage.tsx @@ -2,6 +2,8 @@ import { VNode } from "preact"; import Loading from "components/loading"; +import { UpgradeState } from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeState"; + export const LoadingPage = ({ title, description, @@ -10,10 +12,8 @@ export const LoadingPage = ({ description?: VNode; }) => { return ( -
- -
{title}
- {description &&
{description}
} -
+ }> + {description} + ); }; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/NewVersionAvailable.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/NewVersionAvailable.tsx index 2031f2aa5..1fa267cc4 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/NewVersionAvailable.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/NewVersionAvailable.tsx @@ -1,6 +1,9 @@ import { Trans } from "@lingui/macro"; +import LineChart, { LineChartStep } from "components/PathChart"; + import { useNewVersion } from "plugins/lime-plugin-firmware/src/firmwareQueries"; +import { UpgradeState } from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeState"; import { useBoardData } from "utils/queries"; @@ -12,31 +15,68 @@ export const NewVersionAvailable = ({ const { data: boardData } = useBoardData(); const { data: newVersion } = useNewVersion(); - return ( -
-
- {readyForUpgrade ? ( - Start Mesh Wide Transaction - ) : ( - New version available! - )} -
-
- This node version: -
- {boardData && boardData.release.description} -
-
- New available version: + let steps: LineChartStep[] = [ + { + text: ( + + This node version +
+ {boardData && boardData.release.version} +
+ ), + status: "SUCCESS", + }, + ]; + + if (!readyForUpgrade) { + steps = [ + ...steps, + { + text: ( + + New available version: +
+ {newVersion && newVersion.version} +
+ ), + status: "SUCCESS", + }, + ]; + } else { + steps = [ + ...steps, + { + text: ( + + Downloaded version +
+ {newVersion && newVersion.version} +
+ ), + status: "SUCCESS", + }, + { + text: Start mesh wide upgrade, + status: "SUCCESS", + }, + ]; + } + let title = New version available!; + if (readyForUpgrade) { + title = ( + + Ready to start mesh wide
- {newVersion && newVersion.version} + firmware upgrade +
+ ); + } + + return ( + +
+
- {readyForUpgrade && ( -
- Is ready for upgrade! -
-
- )} -
+ ); }; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/NoNewVersion.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/NoNewVersion.tsx index ed09442ee..4b1638b4c 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/NoNewVersion.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/NoNewVersion.tsx @@ -1,19 +1,15 @@ import { Trans } from "@lingui/macro"; -import { useNewVersion } from "plugins/lime-plugin-firmware/src/firmwareQueries"; - -import { useBoardData } from "utils/queries"; +import { + MeshUpgradeSuccessIcon, + UpgradeState, +} from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeState"; export const NoNewVersionAvailable = () => { - const { data: boardData } = useBoardData(); - const { data: newVersion } = useNewVersion(); - return ( -
-
-
- No new version available! -
-
+ No new version available!} + icon={} + /> ); }; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/TransactionStarted.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/TransactionStarted.tsx index 63de80451..50d72f2fb 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/TransactionStarted.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/TransactionStarted.tsx @@ -1,17 +1,27 @@ +import { Trans } from "@lingui/macro"; + +import { UpgradeState } from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeState"; import { useMeshUpgrade } from "plugins/lime-plugin-mesh-wide-upgrade/src/hooks/meshWideUpgradeProvider"; export const TransactionStarted = () => { const { someNodeDownloading } = useMeshUpgrade(); + const title = ( +
+ Mesh wide upgrade started! +
+ ); return ( -
- Transaction started! + {someNodeDownloading && (
- Some nodes seems to be downloading, check network page for - more information + + Some nodes are still downloading the firmware +
+ check network page for more information +
)} -
+
); }; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeScheduled.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeScheduled.tsx index ac2a9a1f6..ed9d15efb 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeScheduled.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeScheduled.tsx @@ -1,25 +1,25 @@ import { Trans } from "@lingui/macro"; -import { ParallelErrors } from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ParallelErrors"; +import { + ParallelErrors, + UpgradeState, +} from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeState"; import { useMeshUpgrade } from "plugins/lime-plugin-mesh-wide-upgrade/src/hooks/meshWideUpgradeProvider"; import { useParallelScheduleUpgrade } from "plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeQueries"; export const UpgradeScheduled = () => { const { totalNodes } = useMeshUpgrade(); const { errors, results } = useParallelScheduleUpgrade(); - const nodesToBeUpgraded = results.length; + const nodesToBeUpgraded = results?.length; return ( - <> -
- Upgrade is scheduled! -
-
+ Upgrade is scheduled!}> + <> {nodesToBeUpgraded} of {totalNodes} will be upgraded -
- {errors?.length > 0 && } - + {errors?.length > 0 && } + + ); }; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ParallelErrors.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeState.tsx similarity index 54% rename from plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ParallelErrors.tsx rename to plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeState.tsx index ce30e1035..3e98c37e9 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ParallelErrors.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/UpgradeState.tsx @@ -1,9 +1,32 @@ import { Trans } from "@lingui/macro"; +import { ComponentChildren } from "preact"; +import { GlobeIcon } from "components/icons/globeIcon"; +import { Tick, Warning } from "components/icons/status"; import { StatusMessage } from "components/status/statusMessage"; import { RemoteNodeCallError, SyncCallErrors } from "utils/meshWideSyncCall"; +interface UpgradeStateProps { + icon?: ComponentChildren; + title: ComponentChildren; + children?: ComponentChildren; +} + +export const UpgradeState = ({ + icon = , + title, + children, +}: UpgradeStateProps) => { + return ( +
+ {icon} +
{title}
+ {children &&
{children}
} +
+ ); +}; + export const ParallelErrors = ({ errors }: { errors: SyncCallErrors }) => { return ( // Important to not add any style to this fragment because could add errors on growing on long lists @@ -26,3 +49,11 @@ export const ParallelErrors = ({ errors }: { errors: SyncCallErrors }) => { ); }; + +export const MeshUpgradeSuccessIcon = () => { + return ; +}; + +export const MeshUpgradeErrorIcon = () => { + return ; +}; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/containers/meshWideUpgradeStatus.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/containers/meshWideUpgradeStatus.tsx index 036ab60cf..8588d8d16 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/containers/meshWideUpgradeStatus.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/containers/meshWideUpgradeStatus.tsx @@ -1,6 +1,7 @@ import { Trans } from "@lingui/macro"; import { ConfirmationPending } from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ConfirmationPending"; +import { Confirmed } from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/Confirmed"; import { ErrorState } from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/ErrorState"; import { LoadingPage } from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/LoadingPage"; import { NewVersionAvailable } from "plugins/lime-plugin-mesh-wide-upgrade/src/components/upgradeState/NewVersionAvailable"; @@ -65,7 +66,7 @@ const MeshWideUpgradeStatusState = () => { case "CONFIRMATION_PENDING": return ; case "CONFIRMED": - return <>Confirmed!; + return ; default: return ; } diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/hooks/meshWideUpgradeProvider.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/hooks/meshWideUpgradeProvider.tsx index 3c52b1224..9692e98f2 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/hooks/meshWideUpgradeProvider.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/hooks/meshWideUpgradeProvider.tsx @@ -1,15 +1,15 @@ import { ComponentChildren, createContext } from "preact"; import { useMemo } from "preact/compat"; -import { useEffect } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { useCallback, useContext } from "react"; import { useNewVersion } from "plugins/lime-plugin-firmware/src/firmwareQueries"; import { getStepperStatus } from "plugins/lime-plugin-mesh-wide-upgrade/src/hooks/useStepper"; import { - useAbort, useBecomeMainNode, useMeshUpgradeNodeStatus, useMeshWideUpgradeInfo, + useParallelAbort, useParallelConfirmUpgrade, useParallelScheduleUpgrade, useStartFirmwareUpgradeTransaction, @@ -70,7 +70,7 @@ export const MeshWideUpgradeProvider = ({ }) => { // UseCallback to invalidate queries const invalidateQueries = useCallback(() => { - queryCache.invalidateQueries({ + return queryCache.invalidateQueries({ queryKey: meshUpgradeQueryKeys.getMeshUpgradeNodeStatus(), }); }, []); @@ -90,21 +90,15 @@ export const MeshWideUpgradeProvider = ({ refetchInterval: NODE_STATUS_REFETCH_INTERVAL, }); - const { mutate: becomeMainNodeMutation } = useBecomeMainNode({ - onSuccess: () => { - invalidateQueries(); - }, - }); + const { mutateAsync: becomeMainNodeMutation } = useBecomeMainNode({}); - const { mutate: fwUpgradeTransaction } = useStartFirmwareUpgradeTransaction( - { - onSuccess: () => { - invalidateQueries(); - }, - } - ); + const { mutateAsync: fwUpgradeTransaction } = + useStartFirmwareUpgradeTransaction({}); - const { mutate: abort, isLoading: isAborting } = useAbort({}); + // Inner state to control is aborting callback awaiting until query invalidation + const [isAborting, setIsAborting] = useState(false); + // const { mutateAsync: abortMutation } = useAbort({}); + const { callMutations: abortMutation } = useParallelAbort(); const { data: session } = useSession(); const { data: newVersionData } = useNewVersion({ @@ -161,13 +155,26 @@ export const MeshWideUpgradeProvider = ({ const meshWideError = getMeshWideError(thisNode); - const becomeMainNode = useCallback(() => { - becomeMainNodeMutation({}); - }, [becomeMainNodeMutation]); - - const startFwUpgradeTransaction = useCallback(() => { - fwUpgradeTransaction({}); - }, [fwUpgradeTransaction]); + const becomeMainNode = useCallback(async () => { + await becomeMainNodeMutation({}); + await invalidateQueries(); + }, [becomeMainNodeMutation, invalidateQueries]); + + const startFwUpgradeTransaction = useCallback(async () => { + await fwUpgradeTransaction({}); + await invalidateQueries(); + }, [fwUpgradeTransaction, invalidateQueries]); + + const abort = useCallback(async () => { + setIsAborting(true); + abortMutation() + .then(() => { + return invalidateQueries(); + }) + .finally(() => { + setIsAborting(false); + }); + }, [abortMutation, invalidateQueries]); const isLoading = meshWideInfoLoading || thisNodeLoading; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/hooks/useStepper.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/hooks/useStepper.tsx index 8b8a351de..65e376640 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/hooks/useStepper.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/hooks/useStepper.tsx @@ -4,6 +4,7 @@ import { useMemo } from "react"; import { IStatusAndButton } from "components/status/statusAndButton"; import { + useAbortModal, useConfirmModal, useScheduleUpgradeModal, } from "plugins/lime-plugin-mesh-wide-upgrade/src/components/modals"; @@ -144,21 +145,26 @@ export const useStep = () => { const { callMutations: startScheduleMeshUpgrade, errors: scheduleErrors } = useParallelScheduleUpgrade(); - const { callMutations: confirmMeshUpgrade, errors: confirmErrors } = - useParallelConfirmUpgrade(); + const { callMutations: confirmMeshUpgrade } = useParallelConfirmUpgrade(); const { showModal: showScheduleModal } = useScheduleUpgradeModal({ - allNodesReady: allNodesReadyForUpgrade, + useSuccessBtn: allNodesReadyForUpgrade, cb: () => { - startScheduleMeshUpgrade(); + return startScheduleMeshUpgrade(); }, }); const { showModal: showConfirmationModal } = useConfirmModal({ // Ideally we have to implement some kind of state before run the upgrade to check if all nodes are up again. - allNodesReady: true, + useSuccessBtn: true, cb: () => { - confirmMeshUpgrade(); + return confirmMeshUpgrade(); + }, + }); + + const { showModal: showAbortModal } = useAbortModal({ + cb: () => { + return abort(); }, }); @@ -249,7 +255,7 @@ export const useStep = () => { > = { btnCancel: Abort, onClickCancel: async () => { - await abort(); + showAbortModal(); }, }; step = { ...step, ...showAbort }; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeApi.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeApi.tsx index 4704d3410..ca55bd97f 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeApi.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeApi.tsx @@ -59,6 +59,13 @@ export async function remoteConfirmUpgrade({ ip }: { ip: string }) { }); } +export async function remoteAbort({ ip }: { ip: string }) { + return await callToRemoteNode({ + ip, + apiCall: (customApi) => meshUpgradeApiCall("abort", customApi), + }); +} + const meshUpgradeApiCall = async ( method: string, customApi?: UhttpdService diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeQueries.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeQueries.tsx index 1a0894e6d..d31e86aba 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeQueries.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeQueries.tsx @@ -3,9 +3,9 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { getMeshUpgradeNodeStatus, getMeshWideUpgradeInfo, + remoteAbort, remoteConfirmUpgrade, remoteScheduleUpgrade, - setAbort, setBecomeMainNode, setStartFirmwareUpgradeTransaction, } from "plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeApi"; @@ -14,7 +14,7 @@ import { MeshWideUpgradeInfo, NodeMeshUpgradeInfo, } from "plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeTypes"; -import { getNodeIpsByStatus } from "plugins/lime-plugin-mesh-wide-upgrade/src/utils/api"; +import { getNodeIpsByCondition } from "plugins/lime-plugin-mesh-wide-upgrade/src/utils/api"; import { useMeshWideSyncCall } from "utils/meshWideSyncCall"; @@ -56,13 +56,6 @@ export function useStartFirmwareUpgradeTransaction(params) { }); } -export function useAbort(params) { - return useMutation({ - mutationFn: setAbort, - ...params, - }); -} - // Parallel queries/mutations export type UseScheduleMeshSafeUpgradeType = ReturnType< @@ -71,7 +64,10 @@ export type UseScheduleMeshSafeUpgradeType = ReturnType< export const useParallelScheduleUpgrade = (opts?) => { // State to store the errors const { data: nodes } = useMeshWideUpgradeInfo({}); - const ips = getNodeIpsByStatus(nodes, "READY_FOR_UPGRADE"); + const ips = getNodeIpsByCondition( + nodes, + (node) => node.upgrade_state === "READY_FOR_UPGRADE" + ); // localStorage.setItem("hideReleaseBannerPlease", value); return useMeshWideSyncCall({ mutationKey: meshUpgradeQueryKeys.remoteScheduleUpgrade(), @@ -87,7 +83,10 @@ export type UseConfirmUpgradeType = ReturnType< export const useParallelConfirmUpgrade = (opts?) => { // State to store the errors const { data: nodes } = useMeshWideUpgradeInfo({}); - const ips = getNodeIpsByStatus(nodes, "CONFIRMATION_PENDING"); + const ips = getNodeIpsByCondition( + nodes, + (node) => node.upgrade_state === "CONFIRMATION_PENDING" + ); return useMeshWideSyncCall({ mutationKey: meshUpgradeQueryKeys.remoteConfirmUpgrade(), mutationFn: remoteConfirmUpgrade, @@ -95,3 +94,22 @@ export const useParallelConfirmUpgrade = (opts?) => { options: opts, }); }; + +export const useParallelAbort = (opts?) => { + // State to store the errors + const { data: nodes } = useMeshWideUpgradeInfo({}); + const ips = getNodeIpsByCondition(nodes, (node) => + [ + "READY_FOR_UPGRADE", + "UPGRADE_SCHEDULED", + "CONFIRMATION_PENDING", + "ERROR", + ].includes(node.upgrade_state) + ); + return useMeshWideSyncCall({ + mutationKey: meshUpgradeQueryKeys.remoteAbort(), + mutationFn: remoteAbort, + ips, + options: opts, + }); +}; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeQueriesKeys.tsx b/plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeQueriesKeys.tsx index dc947d179..fc54018d5 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeQueriesKeys.tsx +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeQueriesKeys.tsx @@ -9,6 +9,7 @@ const MeshUpgradeQueryKeys: MeshUpgradeQueryKeysProps = { getMeshUpgradeNodeStatus: ["lime-mesh-upgrade", "get_node_status"], remoteScheduleUpgrade: ["lime-mesh-upgrade", "schedule_upgrade"], remoteConfirmUpgrade: ["lime-mesh-upgrade", "confirm_boot_partition"], + remoteAbort: ["lime-mesh-upgrade", "abort"], }; export const meshUpgradeQueryKeys = { @@ -20,4 +21,5 @@ export const meshUpgradeQueryKeys = { MeshUpgradeQueryKeys.remoteScheduleUpgrade, remoteConfirmUpgrade: (): QueryKey => MeshUpgradeQueryKeys.remoteConfirmUpgrade, + remoteAbort: (): QueryKey => MeshUpgradeQueryKeys.remoteConfirmUpgrade, }; diff --git a/plugins/lime-plugin-mesh-wide-upgrade/src/utils/api.ts b/plugins/lime-plugin-mesh-wide-upgrade/src/utils/api.ts index 64c6e5ab3..76c7d267a 100644 --- a/plugins/lime-plugin-mesh-wide-upgrade/src/utils/api.ts +++ b/plugins/lime-plugin-mesh-wide-upgrade/src/utils/api.ts @@ -1,7 +1,7 @@ import { MeshUpgradeApiErrorTypes, MeshWideUpgradeInfo, - UpgradeStatusType, + NodeMeshUpgradeInfo, } from "plugins/lime-plugin-mesh-wide-upgrade/src/meshUpgradeTypes"; import { RemoteNodeCallError } from "utils/meshWideSyncCall"; @@ -21,13 +21,14 @@ export class MeshUpgradeApiError extends Error { } /** - * From a MeshWideUpgradeInfo nodes it returns the ips of the nodes that are in certain status provided + * From a MeshWideUpgradeInfo nodes it returns the ips of the nodes that match the condition defined on the function * @param nodes the nodes to check - * @param status the status to check the criteria + * @param condition function that receives a NodeMeshUpgradeInfo and returns a boolean */ -export const getNodeIpsByStatus = ( +export const getNodeIpsByCondition = ( nodes: MeshWideUpgradeInfo, - status: UpgradeStatusType + // status: UpgradeStatusType + condition: (node: NodeMeshUpgradeInfo) => boolean ) => { if (!nodes) return []; return Object.values(nodes) @@ -36,7 +37,7 @@ export const getNodeIpsByStatus = ( node.node_ip !== null && node.node_ip !== undefined && node.node_ip.trim() !== "" && - node.upgrade_state === status + condition(node) ) .map((node) => node.node_ip as string); // 'as string' is safe here due to the filter condition }; diff --git a/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/NodeDetail.tsx b/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/NodeDetail.tsx index 06596cf91..0357bc4f9 100644 --- a/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/NodeDetail.tsx +++ b/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/NodeDetail.tsx @@ -42,7 +42,10 @@ const NodeDetails = ({ actual, reference, name }: NodeMapFeature) => {
{name}
- +
diff --git a/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/UpdateNodeInfoBtn.tsx b/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/UpdateNodeInfoBtn.tsx index b8a8b9dbc..3d3e80e32 100644 --- a/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/UpdateNodeInfoBtn.tsx +++ b/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/UpdateNodeInfoBtn.tsx @@ -13,17 +13,25 @@ import { import { getFromSharedStateKeys } from "plugins/lime-plugin-mesh-wide/src/meshWideQueriesKeys"; import { DataTypes, - INodeInfo, completeDataTypeKeys, } from "plugins/lime-plugin-mesh-wide/src/meshWideTypes"; import queryCache from "utils/queryCache"; -const UpdateNodeInfoBtn = ({ node }: { node: INodeInfo }) => { - const ip = node.ipv4; +interface INodeInfoProps { + ip: string; + nodeName: string; +} +const UpdateNodeInfoBtn = ({ + ip, + nodeName, + updateOnMount = true, +}: { + updateOnMount?: boolean; +} & INodeInfoProps) => { const [isLoading, setIsLoading] = useState(false); - const { showToast, hideToast } = useToast(); + const { showToast } = useToast(); const { mutateAsync: localNodeSync } = useSyncDataTypes({ ip, @@ -52,7 +60,7 @@ const UpdateNodeInfoBtn = ({ node }: { node: INodeInfo }) => { showToast({ text: ( - Error connecting with {node.hostname}, is node up? + Error connecting with {nodeName}, is node up? ), duration: 5000, @@ -71,25 +79,30 @@ const UpdateNodeInfoBtn = ({ node }: { node: INodeInfo }) => { ip, isLoading, localNodeSync, - node.hostname, + nodeName, publishOnRemoteNode, showToast, ]); // Use effect to sync the node data on mount useEffect(() => { + if (!updateOnMount) return; (async () => { await syncNode(); })(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [node.ipv4]); + }, [ip]); return ( diff --git a/src/components/PathChart/index.tsx b/src/components/PathChart/index.tsx new file mode 100644 index 000000000..c88351717 --- /dev/null +++ b/src/components/PathChart/index.tsx @@ -0,0 +1,119 @@ +import { ComponentChildren } from "preact"; +import React from "react"; + +import { GlobeIcon } from "components/icons/globeIcon"; +import LoadingDots from "components/loading/loading-dots"; + +type StepStatus = "SUCCESS" | "ERROR" | "DISABLED"; + +export type LineChartStep = { + text: ComponentChildren; + isLoading?: boolean; + status: StepStatus; +}; + +type CircleProps = { + index: number; + color?: string; + className?: string; + isLast?: boolean; + nextStep?: CircleProps; // Used to get the correct color of the line +} & LineChartStep; + +const getColorByStatus = (step?: CircleProps) => { + let color = "gray-400"; + let lineColor = `bg-gray-400`; + if (step?.status === "SUCCESS") { + if (step.isLast) { + color = "internet"; + lineColor = `bg-internet`; + } else { + color = "primary-dark"; + lineColor = `bg-primary-dark`; + } + } else if (step?.status === "ERROR") { + if (step.isLast) { + color = "danger"; + } else { + color = "danger"; + lineColor = `bg-danger`; + } + } + return { color, lineColor }; +}; + +const LineBall = ({ ...step }: CircleProps) => { + const { color } = getColorByStatus(step); + const { lineColor } = getColorByStatus(step.nextStep); + + const linePart = "h-14"; + + return ( +
+
+ {step.isLast ? ( + + ) : ( +
+ )} +
+
+
+ ); +}; + +const Step = ({ index, ...step }: CircleProps) => { + let color = "gray-400"; + if (step.isLast) { + color = "text-internet"; + } else if (step.status === "SUCCESS") { + color = "text-primary-dark"; + } else if (step.status === "ERROR") { + color = "text-danger"; + } + + const loadingIndicator = step.isLoading ? : null; + + return ( +
+ + + {step.text} {loadingIndicator} + +
+ ); +}; + +function LineChart({ steps }: { steps: LineChartStep[] }) { + if (!steps) return null; + return ( +
+ {steps.map((step, index) => { + const nextStep = steps[index + 1] ?? null; + return ( + + ); + })} +
+ ); +} + +export default LineChart; diff --git a/src/components/buttons/button.tsx b/src/components/buttons/button.tsx index c57255d52..319a674cc 100644 --- a/src/components/buttons/button.tsx +++ b/src/components/buttons/button.tsx @@ -1,7 +1,7 @@ -import React from "react"; +import React, { useCallback } from "react"; export interface ButtonProps { - onClick?: (e) => void; + onClick?: ((e) => void) | ((e) => Promise); children?: any; // type error with Trans component size?: "sm" | "md" | "lg"; color?: "primary" | "secondary" | "danger" | "info" | "disabled"; @@ -20,6 +20,9 @@ export const Button = ({ outline = false, ...props }: ButtonProps) => { + // button internal state to set loading state + const [isLoading, setIsLoading] = React.useState(false); + let sizeClasses = "", colorClasses = ""; switch (size) { @@ -34,7 +37,7 @@ export const Button = ({ break; } - color = disabled ? "disabled" : color; + color = disabled || isLoading ? "disabled" : color; switch (color) { case "secondary": @@ -55,7 +58,7 @@ export const Button = ({ case "disabled": colorClasses = outline ? "border-2 border-button-disabled text-button-disabled hover:bg-button-disabled hover:text-white" - : "bg-button-disabled text-white border-2 border-button-disabled hover:text-button-disabled hover:bg-white"; + : "bg-button-disabled border-2 border-button-disabled hover:text-button-disabled hover:bg-white"; break; case "primary": default: @@ -67,10 +70,21 @@ export const Button = ({ const cls = `cursor-pointer font-semibold rounded-xl text-center place-content-center transition-all duration-300 justify-center border-0 ${sizeClasses} ${colorClasses}`; + + // useCallback for button click + const handleClick = useCallback( + async (e) => { + if (isLoading || disabled) return; + setIsLoading(true); + await onClick(e); + setIsLoading(false); + }, + [disabled, isLoading, onClick] + ); const Btn = () => (
(!disabled ? onClick(e) : null)} + onClick={(e) => handleClick(e)} className={cls} {...props} > diff --git a/src/components/icons/status.tsx b/src/components/icons/status.tsx index 684d146de..82ca516f5 100644 --- a/src/components/icons/status.tsx +++ b/src/components/icons/status.tsx @@ -14,13 +14,17 @@ export const StatusIcon = ({ status }: { status: StatusIcons }) => { } }; -export const Tick = () => ; +interface IconsProps { + className?: string; +} -export const Warning = () => ( +export const Tick = ({ className }: IconsProps) => ( + +); + +export const Warning = ({ className = "w-8 h-8" }: IconsProps) => ( !