diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index be45e2eabf..fe4b9628d6 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -3,6 +3,7 @@ import "reactflow/dist/style.css"; import "./App.css"; import { ErrorBoundary } from "react-error-boundary"; +import { useNavigate } from "react-router-dom"; import ErrorAlert from "./alerts/error"; import NoticeAlert from "./alerts/notice"; import SuccessAlert from "./alerts/success"; @@ -43,6 +44,9 @@ export default function App() { const refreshVersion = useDarkStore((state) => state.refreshVersion); const refreshStars = useDarkStore((state) => state.refreshStars); const checkHasStore = useStoreStore((state) => state.checkHasStore); + const navigate = useNavigate(); + + const [isLoadingHealth, setIsLoadingHealth] = useState(false); useEffect(() => { refreshStars(); @@ -60,11 +64,12 @@ export default function App() { }, [isAuthenticated]); useEffect(() => { + checkApplicationHealth(); // Timer to call getHealth every 5 seconds const timer = setInterval(() => { getHealth() .then(() => { - if (fetchError) setFetchError(false); + onHealthCheck(); }) .catch(() => { setFetchError(true); @@ -77,6 +82,30 @@ export default function App() { }; }, []); + const checkApplicationHealth = () => { + setIsLoadingHealth(true); + getHealth() + .then(() => { + onHealthCheck(); + }) + .catch(() => { + setFetchError(true); + }); + + setTimeout(() => { + setIsLoadingHealth(false); + }, 2000); + }; + + const onHealthCheck = () => { + setFetchError(false); + //This condition is necessary to avoid infinite loop on starter page when the application is not healthy + if (isLoading === true && window.location.pathname === "/") { + navigate("/flows"); + window.location.reload(); + } + }; + return ( //need parent component with width and height
@@ -86,20 +115,29 @@ export default function App() { }} FallbackComponent={CrashErrorComponent} > - {fetchError ? ( - - ) : isLoading ? ( -
- -
- ) : ( - <> - - - )} + <> + { + { + checkApplicationHealth(); + }} + isLoadingHealth={isLoadingHealth} + > + } + + {isLoading ? ( +
+ +
+ ) : ( + <> + + + )} +
diff --git a/src/frontend/src/components/fetchErrorComponent/index.tsx b/src/frontend/src/components/fetchErrorComponent/index.tsx index 6004d9dfca..956de62700 100644 --- a/src/frontend/src/components/fetchErrorComponent/index.tsx +++ b/src/frontend/src/components/fetchErrorComponent/index.tsx @@ -1,16 +1,51 @@ +import BaseModal from "../../modals/baseModal"; import { fetchErrorComponentType } from "../../types/components"; import IconComponent from "../genericIconComponent"; +import { Button } from "../ui/button"; export default function FetchErrorComponent({ message, description, + openModal, + setRetry, + isLoadingHealth, }: fetchErrorComponentType) { return ( -
- -

- {message} - {description} -
+ <> + + +
+ +

+ {message} + + {description} + +
+
+ + +
+ +
+
+
+ ); } diff --git a/src/frontend/src/components/ui/dialog-with-no-close.tsx b/src/frontend/src/components/ui/dialog-with-no-close.tsx new file mode 100644 index 0000000000..929172e008 --- /dev/null +++ b/src/frontend/src/components/ui/dialog-with-no-close.tsx @@ -0,0 +1,119 @@ +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import * as React from "react"; +import { cn } from "../../utils/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = ({ + children, + ...props +}: DialogPrimitive.DialogPortalProps) => ( + +
+ {children} +
+
+); +DialogPortal.displayName = DialogPrimitive.Portal.displayName; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +}; diff --git a/src/frontend/src/modals/baseModal/index.tsx b/src/frontend/src/modals/baseModal/index.tsx index a8a59dad36..08f00ae8d8 100644 --- a/src/frontend/src/modals/baseModal/index.tsx +++ b/src/frontend/src/modals/baseModal/index.tsx @@ -9,6 +9,12 @@ import { DialogTitle, DialogTrigger, } from "../../components/ui/dialog"; + +import { + Dialog as Modal, + DialogContent as ModalContent, +} from "../../components/ui/dialog-with-no-close"; + import { modalHeaderType } from "../../types/components"; import { cn } from "../../utils/utils"; @@ -76,6 +82,7 @@ interface BaseModalProps { disable?: boolean; onChangeOpenModal?: (open?: boolean) => void; + type?: "modal" | "dialog"; } function BaseModal({ open, @@ -83,6 +90,7 @@ function BaseModal({ children, size = "large", onChangeOpenModal, + type = "dialog", }: BaseModalProps) { const headerChild = React.Children.toArray(children).find( (child) => (child as React.ReactElement).type === Header @@ -156,22 +164,43 @@ function BaseModal({ //UPDATE COLORS AND STYLE CLASSSES return ( - - {triggerChild} - -
- {headerChild} -
-
- {ContentChild} -
- {ContentFooter && ( -
{ContentFooter}
- )} -
-
+ <> + {type === "modal" ? ( + + {triggerChild} + +
+ {headerChild} +
+
+ {ContentChild} +
+ {ContentFooter && ( +
{ContentFooter}
+ )} +
+
+ ) : ( + + {triggerChild} + +
+ {headerChild} +
+
+ {ContentChild} +
+ {ContentFooter && ( +
{ContentFooter}
+ )} +
+
+ )} + ); } diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index 5d6a3c77fe..2ca51a4c4d 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -88,6 +88,7 @@ const useFlowsManagerStore = create((set, get) => ({ } }) .catch((e) => { + set({ isLoading: false }); useAlertStore.getState().setErrorData({ title: "Could not load flows from database", }); diff --git a/src/frontend/src/stores/typesStore.ts b/src/frontend/src/stores/typesStore.ts index 557a3a30d6..808a5d6469 100644 --- a/src/frontend/src/stores/typesStore.ts +++ b/src/frontend/src/stores/typesStore.ts @@ -27,10 +27,6 @@ export const useTypesStore = create((set, get) => ({ resolve(); }) .catch((error) => { - useAlertStore.getState().setErrorData({ - title: "An error has occurred while fetching types.", - list: ["Please refresh the page."], - }); console.error("An error has occurred while fetching types."); console.log(error); reject(); diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index ab32dab625..4bdf56b320 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -665,6 +665,9 @@ export type ApiKey = { export type fetchErrorComponentType = { message: string; description: string; + openModal?: boolean; + setRetry: () => void; + isLoadingHealth: boolean; }; export type dropdownButtonPropsType = {