diff --git a/packages/shared/api/portal/index.ts b/packages/shared/api/portal/index.ts index 14e779912a..24857846c9 100644 --- a/packages/shared/api/portal/index.ts +++ b/packages/shared/api/portal/index.ts @@ -27,7 +27,13 @@ import { AxiosRequestConfig } from "axios"; import { EmployeeType } from "../../enums"; import { request } from "../client"; -import { TPaymentQuota, TPortal, TPortalTariff, TTenantExtra } from "./types"; +import { + TPaymentQuota, + TPortal, + TPortalTariff, + TRestoreProgress, + TTenantExtra, +} from "./types"; export function getShortenedLink(link) { return request({ @@ -169,8 +175,13 @@ export function startRestore(backupId, storageType, storageParams, notify) { }); } -export function getRestoreProgress() { - return request({ method: "get", url: "/portal/getrestoreprogress" }); +export async function getRestoreProgress() { + const res = (await request({ + method: "get", + url: "/portal/getrestoreprogress", + })) as TRestoreProgress; + + return res; } export function enableRestore() { diff --git a/packages/shared/api/portal/types.ts b/packages/shared/api/portal/types.ts index 8d8cae7f90..1dd34fcb05 100644 --- a/packages/shared/api/portal/types.ts +++ b/packages/shared/api/portal/types.ts @@ -126,3 +126,8 @@ export type TTenantExtra = { licenseAccept: Date; enableTariffPage: boolean; }; + +export type TRestoreProgress = { + progress: number; + error?: any; +}; diff --git a/packages/shared/components/color-theme/ColorTheme.types.ts b/packages/shared/components/color-theme/ColorTheme.types.ts index fe93edc71f..0f1b0682b4 100644 --- a/packages/shared/components/color-theme/ColorTheme.types.ts +++ b/packages/shared/components/color-theme/ColorTheme.types.ts @@ -99,7 +99,6 @@ export interface ProgressColorTheme extends DefaultColorThemeProps { themeId: ThemeId.Progress; percent?: number; $currentColorScheme?: TColorScheme; - theme: TTheme; } export interface VersionBadgeTheme extends DefaultColorThemeProps { diff --git a/packages/shared/pages/PreparationPortal/StyledPreparationPortal.js b/packages/shared/pages/PreparationPortal/PreparationPortal.styled.ts similarity index 90% rename from packages/shared/pages/PreparationPortal/StyledPreparationPortal.js rename to packages/shared/pages/PreparationPortal/PreparationPortal.styled.ts index 3b172f3bd1..694b7266bc 100644 --- a/packages/shared/pages/PreparationPortal/StyledPreparationPortal.js +++ b/packages/shared/pages/PreparationPortal/PreparationPortal.styled.ts @@ -27,7 +27,10 @@ import styled from "styled-components"; import { tablet } from "@docspace/shared/utils"; -const StyledPreparationPortal = styled.div` +const StyledPreparationPortal = styled.div<{ + errorMessage?: boolean; + isDialog?: boolean; +}>` width: 100%; @media ${tablet} { margin-top: ${(props) => (props.isDialog ? "0px" : "48px")}; @@ -42,7 +45,13 @@ const StyledPreparationPortal = styled.div` line-height: 20px; max-width: 480px; } + .logo-wrapper { + ${(props) => props.isDialog && "display: none"}; + } + #container { + ${(props) => props.isDialog && "margin-top:0"}; + } .preparation-portal_body-wrapper { margin-bottom: 24px; width: 100%; diff --git a/packages/shared/pages/PreparationPortal/PreparationPortal.types.ts b/packages/shared/pages/PreparationPortal/PreparationPortal.types.ts new file mode 100644 index 0000000000..30c4970056 --- /dev/null +++ b/packages/shared/pages/PreparationPortal/PreparationPortal.types.ts @@ -0,0 +1,31 @@ +// (c) Copyright Ascensio System SIA 2009-2024 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +export interface IPreparationPortal { + isDialog?: boolean; + withoutHeader?: boolean; + style?: React.CSSProperties; +} diff --git a/packages/shared/pages/PreparationPortal/PreparationPortal.utils.ts b/packages/shared/pages/PreparationPortal/PreparationPortal.utils.ts new file mode 100644 index 0000000000..7eafa05ba6 --- /dev/null +++ b/packages/shared/pages/PreparationPortal/PreparationPortal.utils.ts @@ -0,0 +1,251 @@ +// (c) Copyright Ascensio System SIA 2009-2024 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +import { getRestoreProgress } from "../../api/portal"; + +const baseSecondMultiplicationFactor = 400; +const baseThirdMultiplicationFactor = 180; +const baseFirstMultiplicationFactor = 700; +const unSizeMultiplicationFactor = 3; +const thirdBound = 98; + +let prevProgress: number = 0; +let requestsCount: number = 0; + +export const clearLocalStorage = () => { + [ + "LocalCopyStorageType", + "LocalCopyFolder", + "LocalCopyStorage", + "LocalCopyThirdPartyStorageType", + "LocalCopyThirdPartyStorageValues", + ].forEach((k) => localStorage.removeItem(k)); +}; +export const reachingSecondBoundary = ( + percentage: number, + secondBound: number, + progressTimerId: ReturnType | null, + setPercent: (progress: number) => void, +) => { + let progress = percentage; + + const delay = baseSecondMultiplicationFactor * unSizeMultiplicationFactor; + + if (progressTimerId) return; + + progressTimerId = setInterval(() => { + progress += 1; + + if (progress !== secondBound) setPercent(progress); + else { + if (progressTimerId) clearInterval(progressTimerId); + progressTimerId = null; + } + }, delay); +}; +export const reachingThirdBoundary = ( + percentage: number, + progressTimerId: ReturnType | null, + setPercent: (progress: number) => void, +) => { + let progress = percentage; + const delay = baseThirdMultiplicationFactor * unSizeMultiplicationFactor; + if (progressTimerId) return; + + progressTimerId = setInterval(() => { + progress += 1; + + if (progress < thirdBound) setPercent(progress); + else { + if (progressTimerId) clearInterval(progressTimerId); + progressTimerId = null; + } + }, delay); +}; + +export const reachingFirstBoundary = ( + percentage: number, + firstBound: number, + progressTimerId: ReturnType | null, + setPercent: (progress: number) => void, +) => { + let progress = percentage; + const delay = baseFirstMultiplicationFactor * unSizeMultiplicationFactor; + + if (progressTimerId) return; + + progressTimerId = setInterval(() => { + progress += 1; + + if (progress !== firstBound) setPercent(progress); + else { + if (progressTimerId) clearInterval(progressTimerId); + progressTimerId = null; + } + }, delay); +}; + +export const returnToPortal = () => { + setTimeout(() => { + window.location.replace("/"); + }, 5000); +}; + +export const getIntervalProgress = async ( + setErrorMessage: (err: any) => void, + clearAllIntervals: VoidFunction, + timerId: ReturnType | null, + progressTimerId: ReturnType | null, + setPercent: (progress: number) => void, + errorInternalServer: string, +) => { + try { + const response = await getRestoreProgress(); + + if (!response) { + setErrorMessage(errorInternalServer); + clearAllIntervals(); + return; + } + + if (response.error) { + if (timerId) clearInterval(timerId); + if (progressTimerId) clearInterval(progressTimerId); + + progressTimerId = null; + timerId = null; + setErrorMessage(response.error); + + return; + } + + const currProgress = response.progress; + console.log("prevProgress", prevProgress); + if (currProgress > 0 && prevProgress !== currProgress) { + setPercent(currProgress); + + if (progressTimerId) clearInterval(progressTimerId); + progressTimerId = null; + } + + prevProgress = currProgress; + + if (currProgress === 100) { + clearAllIntervals(); + clearLocalStorage(); + returnToPortal(); + } + } catch (error: any) { + clearAllIntervals(); + setErrorMessage(error); + } +}; + +export const getRecoveryProgress = async ( + setErrorMessage: (err: any) => void, + errorInternalServer: string, + timerId: ReturnType | null, + progressTimerId: ReturnType | null, + firstBound: number, + setPercent: (progress: number) => void, + clearAllIntervals: VoidFunction, +) => { + const getMessage = (error: any) => { + if (typeof error !== "object") return error; + + return ( + error?.response?.data?.error?.message || + error?.statusText || + error?.message || + t("Common:ErrorInternalServer") + ); + }; + + try { + const response = await getRestoreProgress(); + + if (!response) { + setErrorMessage(errorInternalServer); + return; + } + + const { progress, error } = response; + + if (error) { + setErrorMessage(error); + + return; + } + + if (progress === 100) { + returnToPortal(); + clearLocalStorage(); + } else { + timerId = setInterval( + () => + getIntervalProgress( + setErrorMessage, + clearAllIntervals, + timerId, + progressTimerId, + setPercent, + errorInternalServer, + ), + 1000, + ); + if (progress < firstBound) + reachingFirstBoundary( + progress, + firstBound, + progressTimerId, + setPercent, + ); + } + + setPercent(progress); + } catch (err: unknown) { + const knownError = err as { response: { status: number } }; + + const status = knownError?.response?.status; + const needCreationTableTime = status === 404; + + if (needCreationTableTime && requestsCount < 3) { + requestsCount += 1; + getRecoveryProgress( + setErrorMessage, + errorInternalServer, + timerId, + progressTimerId, + firstBound, + setPercent, + clearAllIntervals, + ); + return; + } + + setErrorMessage(getMessage(err)); + } +}; diff --git a/packages/shared/pages/PreparationPortal/index.js b/packages/shared/pages/PreparationPortal/index.js deleted file mode 100644 index 354064f5e0..0000000000 --- a/packages/shared/pages/PreparationPortal/index.js +++ /dev/null @@ -1,302 +0,0 @@ -// (c) Copyright Ascensio System SIA 2009-2024 -// -// This program is a free software product. -// You can redistribute it and/or modify it under the terms -// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software -// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended -// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of -// any third-party rights. -// -// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see -// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html -// -// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. -// -// The interactive user interfaces in modified source and object code versions of the Program must -// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. -// -// Pursuant to Section 7(b) of the License you must retain the original Product logo when -// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under -// trademark law for use of our trademarks. -// -// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing -// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 -// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode -import React, { useEffect, useState } from "react"; -import { withTranslation } from "react-i18next"; -import PropTypes from "prop-types"; -import { observer, inject } from "mobx-react"; - -import ErrorContainer from "@docspace/shared/components/error-container/ErrorContainer"; - -import { StyledPreparationPortal } from "./StyledPreparationPortal"; -import { Text } from "../../components/text"; -import { getRestoreProgress } from "../../api/portal"; -import { ColorTheme, ThemeId } from "../../components/color-theme"; - -const baseSize = 1073741824; //number of bytes in one GB -const unSizeMultiplicationFactor = 3; -const baseFirstMultiplicationFactor = 700; -const baseSecondMultiplicationFactor = 400; -const baseThirdMultiplicationFactor = 180; -const firstBound = 10, - secondBound = 63, - thirdBound = 98; - -let timerId = null, - progressTimerId = null, - prevProgress; -let requestsCount = 0; - -const PreparationPortal = (props) => { - const { - multiplicationFactor, - t, - withoutHeader, - style, - clearLocalStorage, - isDialog, - } = props; - - const [percent, setPercent] = useState(0); - const [errorMessage, setErrorMessage] = useState(""); - - const clearAllIntervals = () => { - clearInterval(timerId); - clearInterval(progressTimerId); - - progressTimerId = null; - timerId = null; - }; - - const returnToPortal = () => { - setTimeout(() => { - window.location.replace("/"); - }, 5000); - }; - - const reachingFirstBoundary = (percent) => { - let progress = percent; - const delay = baseFirstMultiplicationFactor * multiplicationFactor; - - if (progressTimerId) return; - - progressTimerId = setInterval(() => { - progress += 1; - - if (progress !== firstBound) setPercent(progress); - else { - clearInterval(progressTimerId); - progressTimerId = null; - } - }, delay); - }; - const reachingSecondBoundary = (percent) => { - let progress = percent; - - const delay = baseSecondMultiplicationFactor * multiplicationFactor; - - if (progressTimerId) return; - - progressTimerId = setInterval(() => { - progress += 1; - - if (progress !== secondBound) setPercent(progress); - else { - clearInterval(progressTimerId); - progressTimerId = null; - } - }, delay); - }; - - const reachingThirdBoundary = (percent) => { - let progress = percent; - const delay = baseThirdMultiplicationFactor * multiplicationFactor; - if (progressTimerId) return; - - progressTimerId = setInterval(() => { - progress += 1; - - if (progress < thirdBound) setPercent(progress); - else { - clearInterval(progressTimerId); - progressTimerId = null; - } - }, delay); - }; - useEffect(() => { - if (percent >= firstBound) { - if (percent < secondBound) { - reachingSecondBoundary(percent); - return; - } else reachingThirdBoundary(percent); - } - }, [percent]); - - const getIntervalProgress = async () => { - try { - const response = await getRestoreProgress(); - - if (!response) { - setErrorMessage(t("Common:ErrorInternalServer")); - clearAllIntervals(); - return; - } - - if (response.error) { - clearInterval(timerId); - clearInterval(progressTimerId); - - progressTimerId = null; - timerId = null; - setErrorMessage(response.error); - - return; - } - - const currProgress = response.progress; - - if (currProgress > 0 && prevProgress !== currProgress) { - setPercent(currProgress); - - clearInterval(progressTimerId); - progressTimerId = null; - } - - prevProgress = currProgress; - - if (currProgress === 100) { - clearAllIntervals(); - clearLocalStorage(); - returnToPortal(); - } - } catch (error) { - clearAllIntervals(); - setErrorMessage(error); - } - }; - - const getRecoveryProgress = async () => { - const errorMessage = (error) => { - if (typeof error !== "object") return error; - - return ( - error?.response?.data?.error?.message || - error?.statusText || - error?.message || - t("Common:ErrorInternalServer") - ); - }; - - try { - const response = await getRestoreProgress(); - - if (!response) { - setErrorMessage(t("Common:ErrorInternalServer")); - return; - } - const { error, progress } = response; - - if (error) { - setErrorMessage(response.error); - - return; - } - - if (progress === 100) { - returnToPortal(); - clearLocalStorage(); - } else { - timerId = setInterval(() => getIntervalProgress(), 1000); - if (progress < firstBound) reachingFirstBoundary(progress); - } - - setPercent(progress); - } catch (err) { - const status = err?.response?.status; - const needCreationTableTime = status === 404; - - if (needCreationTableTime && requestsCount < 3) { - requestsCount++; - getRecoveryProgress(); - return; - } - - setErrorMessage(errorMessage(err)); - } - }; - useEffect(() => { - setTimeout(() => { - getRecoveryProgress(); - }, 6000); - - return () => { - clearAllIntervals(); - }; - }, []); - - const headerText = errorMessage - ? t("Common:Error") - : t("Common:PreparationPortalTitle"); - - return ( - - -
- {errorMessage ? ( - {`${errorMessage}`} - ) : ( - -
-
-
-
- {`${percent} %`} -
- - {t("PreparationPortalDescription")} - -
- )} -
-
-
- ); -}; - -const PreparationPortalWrapper = inject(({ backup }) => { - const { backupSize, clearLocalStorage } = backup; - - const multiplicationFactor = backupSize - ? backupSize / baseSize - : unSizeMultiplicationFactor; - - return { - clearLocalStorage, - multiplicationFactor, - }; -})( - withTranslation(["PreparationPortal", "Common"])(observer(PreparationPortal)), -); - -PreparationPortal.propTypes = { - withoutHeader: PropTypes.bool, - isDialog: PropTypes.bool, -}; - -PreparationPortal.defaultProps = { - withoutHeader: false, - isDialog: false, -}; - -export default (props) => ; diff --git a/packages/shared/pages/PreparationPortal/index.tsx b/packages/shared/pages/PreparationPortal/index.tsx new file mode 100644 index 0000000000..37f1bb6f5d --- /dev/null +++ b/packages/shared/pages/PreparationPortal/index.tsx @@ -0,0 +1,128 @@ +// (c) Copyright Ascensio System SIA 2009-2024 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import ErrorContainer from "@docspace/shared/components/error-container/ErrorContainer"; + +import { StyledPreparationPortal } from "./PreparationPortal.styled"; +import { Text } from "../../components/text"; + +import { ColorTheme, ThemeId } from "../../components/color-theme"; +import { IPreparationPortal } from "./PreparationPortal.types"; +import { + getRecoveryProgress, + reachingSecondBoundary, + reachingThirdBoundary, +} from "./PreparationPortal.utils"; + +const firstBound = 10; +const secondBound = 63; + +let timerId: ReturnType | null; +let progressTimerId: ReturnType | null; + +const PreparationPortal = (props: IPreparationPortal) => { + const { withoutHeader, style, isDialog } = props; + + const { t } = useTranslation(["PreparationPortal", "Common"]); + + const errorInternalServer = t("Common:ErrorInternalServer"); + + const [percent, setPercent] = useState(0); + const [errorMessage, setErrorMessage] = useState(""); + + const clearAllIntervals = () => { + if (timerId) clearInterval(timerId); + if (progressTimerId) clearInterval(progressTimerId); + + progressTimerId = null; + timerId = null; + }; + + useEffect(() => { + if (percent < firstBound) return; + + if (percent < secondBound) { + reachingSecondBoundary(percent, secondBound, progressTimerId, setPercent); + return; + } + + reachingThirdBoundary(percent, progressTimerId, setPercent); + }, [percent]); + + useEffect(() => { + setTimeout(() => { + getRecoveryProgress( + setErrorMessage, + errorInternalServer, + timerId, + progressTimerId, + firstBound, + setPercent, + clearAllIntervals, + ); + }, 6000); + + return () => { + clearAllIntervals(); + }; + }, [errorInternalServer]); + + const headerText = errorMessage + ? t("Common:Error") + : t("Common:PreparationPortalTitle"); + + return ( + + +
+ {errorMessage ? ( + {`${errorMessage}`} + ) : ( + +
+
+
+
+ {`${percent} %`} +
+ + {t("PreparationPortalDescription")} + + + )} +
+ + + ); +}; + +export default PreparationPortal;