feat: add timeout handler on FE (#3537)
* ✨ (frontend): Add TimeoutErrorComponent to handle timeout errors in API requests 🔧 (frontend): Add healthCheckTimeout state and setHealthCheckTimeout function to utilityStore for managing timeout errors in API requests * 📝 (constants.ts): add constants for server health check intervals to improve code readability and maintainability 🐛 (use-get-health.ts): fix refetch interval value to use the newly added constant REFETCH_SERVER_HEALTH_INTERVAL for consistency and easier maintenance * 📝 (api.tsx): Add utilityStore import to improve code organization 📝 (api.tsx): Add createNewError503 function to handle custom 503 errors 📝 (api.tsx): Add AxiosError import for type checking 📝 (api.tsx): Refactor error handling logic for authentication errors 📝 (use-get-health.ts): Add createNewError503 import for custom 503 errors 📝 (use-get-health.ts): Add AxiosError import for type checking 📝 (use-get-health.ts): Refactor error handling logic for server busy status 📝 (index.tsx): Add AxiosError import for type checking 📝 (index.tsx): Add useEffect and useState imports for state management 📝 (index.tsx): Refactor error handling logic for server status and retries 📝 (utilityStore.ts): Add retriesApiRequest state and setRetriesApiRequest function 📝 (axios-error-503.ts): Create function to generate custom 503 error responses 📝 (index.ts): Add retriesApiRequest state and setRetriesApiRequest function to UtilityStoreType * 🔧 (utilityStore.ts): remove unused retriesApiRequest and setRetriesApiRequest functions to clean up code and improve maintainability 🔧 (index.ts): remove unused retriesApiRequest and setRetriesApiRequest types to keep type definitions consistent and up to date * ✨ (nginx.conf): add new health endpoint to proxy_pass requests to the backend server for health checks * ✨ (langflow): Add support for configuring the number of retries for the health check feature. This change introduces a new option `health_check_max_retries` that can be set via environment variable or command line argument to control the maximum number of retries for the health check process. * check if value is none
This commit is contained in:
parent
9bf72ab444
commit
a763f57af5
17 changed files with 229 additions and 29 deletions
|
|
@ -20,6 +20,9 @@ server {
|
||||||
location /health_check {
|
location /health_check {
|
||||||
proxy_pass __BACKEND_URL__;
|
proxy_pass __BACKEND_URL__;
|
||||||
}
|
}
|
||||||
|
location /health {
|
||||||
|
proxy_pass __BACKEND_URL__;
|
||||||
|
}
|
||||||
|
|
||||||
include /etc/nginx/extra-conf.d/*.conf;
|
include /etc/nginx/extra-conf.d/*.conf;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,11 @@ def run(
|
||||||
help="Defines the debounce time for the auto save.",
|
help="Defines the debounce time for the auto save.",
|
||||||
envvar="LANGFLOW_AUTO_SAVING_INTERVAL",
|
envvar="LANGFLOW_AUTO_SAVING_INTERVAL",
|
||||||
),
|
),
|
||||||
|
health_check_max_retries: bool = typer.Option(
|
||||||
|
True,
|
||||||
|
help="Defines the number of retries for the health check.",
|
||||||
|
envvar="LANGFLOW_HEALTH_CHECK_MAX_RETRIES",
|
||||||
|
),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Run Langflow.
|
Run Langflow.
|
||||||
|
|
@ -149,6 +154,7 @@ def run(
|
||||||
store=store,
|
store=store,
|
||||||
auto_saving=auto_saving,
|
auto_saving=auto_saving,
|
||||||
auto_saving_interval=auto_saving_interval,
|
auto_saving_interval=auto_saving_interval,
|
||||||
|
health_check_max_retries=health_check_max_retries,
|
||||||
)
|
)
|
||||||
# create path object if path is provided
|
# create path object if path is provided
|
||||||
static_files_dir: Optional[Path] = Path(path) if path else None
|
static_files_dir: Optional[Path] = Path(path) if path else None
|
||||||
|
|
|
||||||
|
|
@ -340,3 +340,4 @@ class ConfigResponse(BaseModel):
|
||||||
frontend_timeout: int
|
frontend_timeout: int
|
||||||
auto_saving: bool
|
auto_saving: bool
|
||||||
auto_saving_interval: int
|
auto_saving_interval: int
|
||||||
|
health_check_max_retries: int
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,8 @@ class Settings(BaseSettings):
|
||||||
"""If set to True, Langflow will auto save flows."""
|
"""If set to True, Langflow will auto save flows."""
|
||||||
auto_saving_interval: int = 300
|
auto_saving_interval: int = 300
|
||||||
"""The interval in ms at which Langflow will auto save flows."""
|
"""The interval in ms at which Langflow will auto save flows."""
|
||||||
|
health_check_max_retries: int = 5
|
||||||
|
"""The maximum number of retries for the health check."""
|
||||||
|
|
||||||
@field_validator("dev")
|
@field_validator("dev")
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
||||||
|
|
@ -430,6 +430,7 @@ def update_settings(
|
||||||
store: bool = True,
|
store: bool = True,
|
||||||
auto_saving: bool = True,
|
auto_saving: bool = True,
|
||||||
auto_saving_interval: int = 300,
|
auto_saving_interval: int = 300,
|
||||||
|
health_check_max_retries: int = 5,
|
||||||
):
|
):
|
||||||
"""Update the settings from a config file."""
|
"""Update the settings from a config file."""
|
||||||
from langflow.services.utils import initialize_settings_service
|
from langflow.services.utils import initialize_settings_service
|
||||||
|
|
@ -456,9 +457,12 @@ def update_settings(
|
||||||
if not auto_saving:
|
if not auto_saving:
|
||||||
logger.debug("Setting auto_saving to False")
|
logger.debug("Setting auto_saving to False")
|
||||||
settings_service.settings.update_settings(auto_saving=False)
|
settings_service.settings.update_settings(auto_saving=False)
|
||||||
if auto_saving_interval:
|
if auto_saving_interval is not None:
|
||||||
logger.debug(f"Setting auto_saving_interval to {auto_saving_interval}")
|
logger.debug(f"Setting auto_saving_interval to {auto_saving_interval}")
|
||||||
settings_service.settings.update_settings(auto_saving_interval=auto_saving_interval)
|
settings_service.settings.update_settings(auto_saving_interval=auto_saving_interval)
|
||||||
|
if health_check_max_retries is not None:
|
||||||
|
logger.debug(f"Setting health_check_max_retries to {health_check_max_retries}")
|
||||||
|
settings_service.settings.update_settings(health_check_max_retries=health_check_max_retries)
|
||||||
|
|
||||||
|
|
||||||
def is_class_method(func, cls):
|
def is_class_method(func, cls):
|
||||||
|
|
|
||||||
37
src/frontend/src/components/timeoutErrorComponent/index.tsx
Normal file
37
src/frontend/src/components/timeoutErrorComponent/index.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import BaseModal from "../../modals/baseModal";
|
||||||
|
import { fetchErrorComponentType } from "../../types/components";
|
||||||
|
import IconComponent from "../genericIconComponent";
|
||||||
|
import Loading from "../ui/loading";
|
||||||
|
|
||||||
|
export default function TimeoutErrorComponent({
|
||||||
|
message,
|
||||||
|
description,
|
||||||
|
openModal,
|
||||||
|
setRetry,
|
||||||
|
}: fetchErrorComponentType) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BaseModal
|
||||||
|
size="small-h-full"
|
||||||
|
open={openModal}
|
||||||
|
type="modal"
|
||||||
|
onSubmit={() => {
|
||||||
|
setRetry();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BaseModal.Content>
|
||||||
|
<div role="status" className="m-auto flex flex-col items-center">
|
||||||
|
<Loading className={`h-16 w-16`} />
|
||||||
|
<br></br>
|
||||||
|
<span className="text-lg text-primary">{message}</span>
|
||||||
|
<span className="text-center text-lg text-primary">
|
||||||
|
{description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</BaseModal.Content>
|
||||||
|
|
||||||
|
<BaseModal.Footer />
|
||||||
|
</BaseModal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -623,6 +623,10 @@ export const FETCH_ERROR_MESSAGE = "Couldn't establish a connection.";
|
||||||
export const FETCH_ERROR_DESCRIPION =
|
export const FETCH_ERROR_DESCRIPION =
|
||||||
"Check if everything is working properly and try again.";
|
"Check if everything is working properly and try again.";
|
||||||
|
|
||||||
|
export const TIMEOUT_ERROR_MESSAGE =
|
||||||
|
"Please wait a few seconds to server process your request.";
|
||||||
|
export const TIMEOUT_ERROR_DESCRIPION = "Server is busy.";
|
||||||
|
|
||||||
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
|
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
|
||||||
|
|
||||||
export const API_PAGE_PARAGRAPH =
|
export const API_PAGE_PARAGRAPH =
|
||||||
|
|
@ -890,3 +894,6 @@ export const NODE_WIDTH = 400;
|
||||||
export const NODE_HEIGHT = NODE_WIDTH * 3;
|
export const NODE_HEIGHT = NODE_WIDTH * 3;
|
||||||
|
|
||||||
export const SHORTCUT_KEYS = ["cmd", "ctrl", "alt", "shift"];
|
export const SHORTCUT_KEYS = ["cmd", "ctrl", "alt", "shift"];
|
||||||
|
|
||||||
|
export const SERVER_HEALTH_INTERVAL = 10000;
|
||||||
|
export const REFETCH_SERVER_HEALTH_INTERVAL = 20000;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { LANGFLOW_ACCESS_TOKEN } from "@/constants/constants";
|
import { LANGFLOW_ACCESS_TOKEN } from "@/constants/constants";
|
||||||
import useAuthStore from "@/stores/authStore";
|
import useAuthStore from "@/stores/authStore";
|
||||||
|
import { useUtilityStore } from "@/stores/utilityStore";
|
||||||
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from "axios";
|
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from "axios";
|
||||||
import { useContext, useEffect } from "react";
|
import { useContext, useEffect } from "react";
|
||||||
import { Cookies } from "react-cookie";
|
import { Cookies } from "react-cookie";
|
||||||
|
|
@ -29,10 +30,10 @@ function ApiInterceptor() {
|
||||||
const interceptor = api.interceptors.response.use(
|
const interceptor = api.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
async (error: AxiosError) => {
|
async (error: AxiosError) => {
|
||||||
if (
|
const isAuthenticationError =
|
||||||
error?.response?.status === 403 ||
|
error?.response?.status === 403 || error?.response?.status === 401;
|
||||||
error?.response?.status === 401
|
|
||||||
) {
|
if (isAuthenticationError) {
|
||||||
if (!autoLogin) {
|
if (!autoLogin) {
|
||||||
if (error?.config?.url?.includes("github")) {
|
if (error?.config?.url?.includes("github")) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|
@ -50,11 +51,10 @@ function ApiInterceptor() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await clearBuildVerticesState(error);
|
await clearBuildVerticesState(error);
|
||||||
if (
|
|
||||||
error?.response?.status !== 401 &&
|
if (!isAuthenticationError) {
|
||||||
error?.response?.status !== 403
|
|
||||||
) {
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export interface ConfigResponse {
|
||||||
frontend_timeout: number;
|
frontend_timeout: number;
|
||||||
auto_saving: boolean;
|
auto_saving: boolean;
|
||||||
auto_saving_interval: number;
|
auto_saving_interval: number;
|
||||||
|
health_check_max_retries: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGetConfigQuery: useQueryFunctionType<
|
export const useGetConfigQuery: useQueryFunctionType<
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
|
import {
|
||||||
|
REFETCH_SERVER_HEALTH_INTERVAL,
|
||||||
|
SERVER_HEALTH_INTERVAL,
|
||||||
|
} from "@/constants/constants";
|
||||||
|
import { useUtilityStore } from "@/stores/utilityStore";
|
||||||
|
import { createNewError503 } from "@/types/factory/axios-error-503";
|
||||||
import { keepPreviousData } from "@tanstack/react-query";
|
import { keepPreviousData } from "@tanstack/react-query";
|
||||||
|
import { AxiosError, AxiosHeaders } from "axios";
|
||||||
import { useQueryFunctionType } from "../../../../types/api";
|
import { useQueryFunctionType } from "../../../../types/api";
|
||||||
import { api } from "../../api";
|
import { api } from "../../api";
|
||||||
import { UseRequestProcessor } from "../../services/request-processor";
|
import { UseRequestProcessor } from "../../services/request-processor";
|
||||||
|
|
@ -16,20 +23,44 @@ export const useGetHealthQuery: useQueryFunctionType<
|
||||||
getHealthResponse
|
getHealthResponse
|
||||||
> = (_, options) => {
|
> = (_, options) => {
|
||||||
const { query } = UseRequestProcessor();
|
const { query } = UseRequestProcessor();
|
||||||
|
const setHealthCheckTimeout = useUtilityStore(
|
||||||
|
(state) => state.setHealthCheckTimeout,
|
||||||
|
);
|
||||||
|
const healthCheckTimeout = useUtilityStore(
|
||||||
|
(state) => state.healthCheckTimeout,
|
||||||
|
);
|
||||||
/**
|
/**
|
||||||
* Fetches the health status of the API.
|
* Fetches the health status of the API.
|
||||||
*
|
*
|
||||||
* @returns {Promise<AxiosResponse<TransactionsResponse>>} A promise that resolves to an AxiosResponse containing the health status.
|
* @returns {Promise<AxiosResponse<TransactionsResponse>>} A promise that resolves to an AxiosResponse containing the health status.
|
||||||
*/
|
*/
|
||||||
async function getHealthFn() {
|
async function getHealthFn() {
|
||||||
return (await api.get("/health_check")).data;
|
try {
|
||||||
// Health is the only endpoint that doesn't require /api/v1
|
const timeoutPromise = new Promise<never>((_, reject) =>
|
||||||
|
setTimeout(() => reject(createNewError503()), SERVER_HEALTH_INTERVAL),
|
||||||
|
);
|
||||||
|
|
||||||
|
const apiPromise = api.get<{ data: getHealthResponse }>("/health");
|
||||||
|
const response = await Promise.race([apiPromise, timeoutPromise]);
|
||||||
|
setHealthCheckTimeout(null);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
const isServerBusy =
|
||||||
|
healthCheckTimeout === null &&
|
||||||
|
(error as AxiosError)?.response?.status === 503;
|
||||||
|
|
||||||
|
if (isServerBusy) {
|
||||||
|
setHealthCheckTimeout("timeout");
|
||||||
|
} else if (healthCheckTimeout === null) {
|
||||||
|
setHealthCheckTimeout("serverDown");
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryResult = query(["useGetHealthQuery"], getHealthFn, {
|
const queryResult = query(["useGetHealthQuery"], getHealthFn, {
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
refetchInterval: 20000,
|
refetchInterval: REFETCH_SERVER_HEALTH_INTERVAL,
|
||||||
retry: false,
|
retry: false,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ function useSaveConfig() {
|
||||||
const setAutoSavingInterval = useFlowsManagerStore(
|
const setAutoSavingInterval = useFlowsManagerStore(
|
||||||
(state) => state.setAutoSavingInterval,
|
(state) => state.setAutoSavingInterval,
|
||||||
);
|
);
|
||||||
|
const setHealthCheckMaxRetries = useFlowsManagerStore(
|
||||||
|
(state) => state.setHealthCheckMaxRetries,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
|
|
@ -19,6 +22,7 @@ function useSaveConfig() {
|
||||||
axios.defaults.timeout = timeoutInMilliseconds;
|
axios.defaults.timeout = timeoutInMilliseconds;
|
||||||
setAutoSaving(data.auto_saving);
|
setAutoSaving(data.auto_saving);
|
||||||
setAutoSavingInterval(data.auto_saving_interval);
|
setAutoSavingInterval(data.auto_saving_interval);
|
||||||
|
setHealthCheckMaxRetries(data.health_check_max_retries);
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,20 @@ import AlertDisplayArea from "@/alerts/displayArea";
|
||||||
import CrashErrorComponent from "@/components/crashErrorComponent";
|
import CrashErrorComponent from "@/components/crashErrorComponent";
|
||||||
import FetchErrorComponent from "@/components/fetchErrorComponent";
|
import FetchErrorComponent from "@/components/fetchErrorComponent";
|
||||||
import LoadingComponent from "@/components/loadingComponent";
|
import LoadingComponent from "@/components/loadingComponent";
|
||||||
|
import TimeoutErrorComponent from "@/components/timeoutErrorComponent";
|
||||||
import {
|
import {
|
||||||
FETCH_ERROR_DESCRIPION,
|
FETCH_ERROR_DESCRIPION,
|
||||||
FETCH_ERROR_MESSAGE,
|
FETCH_ERROR_MESSAGE,
|
||||||
|
TIMEOUT_ERROR_DESCRIPION,
|
||||||
|
TIMEOUT_ERROR_MESSAGE,
|
||||||
} from "@/constants/constants";
|
} from "@/constants/constants";
|
||||||
import { useGetHealthQuery } from "@/controllers/API/queries/health";
|
import { useGetHealthQuery } from "@/controllers/API/queries/health";
|
||||||
import useTrackLastVisitedPath from "@/hooks/use-track-last-visited-path";
|
import useTrackLastVisitedPath from "@/hooks/use-track-last-visited-path";
|
||||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||||
|
import { useUtilityStore } from "@/stores/utilityStore";
|
||||||
import { cn } from "@/utils/utils";
|
import { cn } from "@/utils/utils";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
|
|
||||||
|
|
@ -17,12 +23,86 @@ export function AppWrapperPage() {
|
||||||
useTrackLastVisitedPath();
|
useTrackLastVisitedPath();
|
||||||
|
|
||||||
const isLoading = useFlowsManagerStore((state) => state.isLoading);
|
const isLoading = useFlowsManagerStore((state) => state.isLoading);
|
||||||
|
|
||||||
|
const healthCheckMaxRetries = useFlowsManagerStore(
|
||||||
|
(state) => state.healthCheckMaxRetries,
|
||||||
|
);
|
||||||
|
|
||||||
|
const healthCheckTimeout = useUtilityStore(
|
||||||
|
(state) => state.healthCheckTimeout,
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: healthData,
|
data: healthData,
|
||||||
isFetching: fetchingHealth,
|
isFetching: fetchingHealth,
|
||||||
isError: isErrorHealth,
|
isError: isErrorHealth,
|
||||||
|
error,
|
||||||
refetch,
|
refetch,
|
||||||
} = useGetHealthQuery();
|
} = useGetHealthQuery();
|
||||||
|
|
||||||
|
const isServerDown =
|
||||||
|
isErrorHealth ||
|
||||||
|
(healthData && Object.values(healthData).some((value) => value !== "ok")) ||
|
||||||
|
healthCheckTimeout === "serverDown";
|
||||||
|
|
||||||
|
const isTimeoutResponseServer = healthCheckTimeout === "timeout";
|
||||||
|
|
||||||
|
const [retryCount, setRetryCount] = useState(0);
|
||||||
|
|
||||||
|
console.log(healthCheckMaxRetries);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isServerBusy =
|
||||||
|
(error as AxiosError)?.response?.status === 503 ||
|
||||||
|
(error as AxiosError)?.response?.status === 429;
|
||||||
|
|
||||||
|
if (isServerBusy && isErrorHealth) {
|
||||||
|
const maxRetries = healthCheckMaxRetries;
|
||||||
|
if (retryCount < maxRetries) {
|
||||||
|
const delay = Math.pow(2, retryCount) * 1000;
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
refetch();
|
||||||
|
setRetryCount(retryCount + 1);
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setRetryCount(0);
|
||||||
|
}
|
||||||
|
}, [isErrorHealth, retryCount, refetch]);
|
||||||
|
|
||||||
|
const modalErrorComponent = useMemo(() => {
|
||||||
|
switch (healthCheckTimeout) {
|
||||||
|
case "serverDown":
|
||||||
|
return (
|
||||||
|
<FetchErrorComponent
|
||||||
|
description={FETCH_ERROR_DESCRIPION}
|
||||||
|
message={FETCH_ERROR_MESSAGE}
|
||||||
|
openModal={isServerDown}
|
||||||
|
setRetry={() => {
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
|
isLoadingHealth={fetchingHealth}
|
||||||
|
></FetchErrorComponent>
|
||||||
|
);
|
||||||
|
case "timeout":
|
||||||
|
return (
|
||||||
|
<TimeoutErrorComponent
|
||||||
|
description={TIMEOUT_ERROR_MESSAGE}
|
||||||
|
message={TIMEOUT_ERROR_DESCRIPION}
|
||||||
|
openModal={isTimeoutResponseServer}
|
||||||
|
setRetry={() => {
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
|
isLoadingHealth={fetchingHealth}
|
||||||
|
></TimeoutErrorComponent>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [healthCheckTimeout, fetchingHealth]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
|
|
@ -32,21 +112,7 @@ export function AppWrapperPage() {
|
||||||
FallbackComponent={CrashErrorComponent}
|
FallbackComponent={CrashErrorComponent}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{
|
{modalErrorComponent}
|
||||||
<FetchErrorComponent
|
|
||||||
description={FETCH_ERROR_DESCRIPION}
|
|
||||||
message={FETCH_ERROR_MESSAGE}
|
|
||||||
openModal={
|
|
||||||
isErrorHealth ||
|
|
||||||
(healthData &&
|
|
||||||
Object.values(healthData).some((value) => value !== "ok"))
|
|
||||||
}
|
|
||||||
setRetry={() => {
|
|
||||||
refetch();
|
|
||||||
}}
|
|
||||||
isLoadingHealth={fetchingHealth}
|
|
||||||
></FetchErrorComponent>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ const past = {};
|
||||||
const future = {};
|
const future = {};
|
||||||
|
|
||||||
const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
||||||
|
healthCheckMaxRetries: 5,
|
||||||
|
setHealthCheckMaxRetries: (healthCheckMaxRetries: number) =>
|
||||||
|
set({ healthCheckMaxRetries }),
|
||||||
autoSaving: true,
|
autoSaving: true,
|
||||||
setAutoSaving: (autoSaving: boolean) => set({ autoSaving }),
|
setAutoSaving: (autoSaving: boolean) => set({ autoSaving }),
|
||||||
autoSavingInterval: SAVE_DEBOUNCE_TIME,
|
autoSavingInterval: SAVE_DEBOUNCE_TIME,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { UtilityStoreType } from "@/types/zustand/utility";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
|
||||||
export const useUtilityStore = create<any>((set, get) => ({
|
export const useUtilityStore = create<UtilityStoreType>((set, get) => ({
|
||||||
selectedItems: [],
|
selectedItems: [],
|
||||||
setSelectedItems: (itemId) => {
|
setSelectedItems: (itemId) => {
|
||||||
if (get().selectedItems.includes(itemId)) {
|
if (get().selectedItems.includes(itemId)) {
|
||||||
|
|
@ -11,4 +12,7 @@ export const useUtilityStore = create<any>((set, get) => ({
|
||||||
set({ selectedItems: get().selectedItems.concat(itemId) });
|
set({ selectedItems: get().selectedItems.concat(itemId) });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
healthCheckTimeout: null,
|
||||||
|
setHealthCheckTimeout: (timeout: string | null) =>
|
||||||
|
set({ healthCheckTimeout: timeout }),
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
23
src/frontend/src/types/factory/axios-error-503.ts
Normal file
23
src/frontend/src/types/factory/axios-error-503.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { AxiosError, AxiosHeaders } from "axios";
|
||||||
|
|
||||||
|
export const createNewError503 = (): AxiosError => {
|
||||||
|
const headers = new AxiosHeaders({
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
url: "/",
|
||||||
|
method: "get",
|
||||||
|
headers: headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
const error = new AxiosError("Server Busy", "ECONNABORTED", config, null, {
|
||||||
|
status: 503,
|
||||||
|
statusText: "Service Unavailable",
|
||||||
|
data: "Server is currently busy, please try again later.",
|
||||||
|
headers: {},
|
||||||
|
config: config,
|
||||||
|
});
|
||||||
|
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
@ -24,6 +24,8 @@ export type FlowsManagerStoreType = {
|
||||||
setSelectedFlowsComponentsCards: (selected: string[]) => void;
|
setSelectedFlowsComponentsCards: (selected: string[]) => void;
|
||||||
autoSavingInterval: number;
|
autoSavingInterval: number;
|
||||||
setAutoSavingInterval: (autoSavingInterval: number) => void;
|
setAutoSavingInterval: (autoSavingInterval: number) => void;
|
||||||
|
healthCheckMaxRetries: number;
|
||||||
|
setHealthCheckMaxRetries: (healthCheckMaxRetries: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UseUndoRedoOptions = {
|
export type UseUndoRedoOptions = {
|
||||||
|
|
|
||||||
6
src/frontend/src/types/zustand/utility/index.ts
Normal file
6
src/frontend/src/types/zustand/utility/index.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export type UtilityStoreType = {
|
||||||
|
selectedItems: any[];
|
||||||
|
setSelectedItems: (itemId: any) => void;
|
||||||
|
healthCheckTimeout: string | null;
|
||||||
|
setHealthCheckTimeout: (timeout: string | null) => void;
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue