mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 16:59:12 +00:00
fix(web): implement offline detection (#1065)
* xtreme connected edition * change fallbackRender to fallbackComponent on ErrorBoundary * call healthz endpoint when error is 500 * display custom offline message * fix eslint indentation for switchCase * Update ErrorPage.tsx * check against error.cause --------- Co-authored-by: Fabricio Silva <hi@fabricio.dev>
This commit is contained in:
parent
d187daa566
commit
a1a16adbab
4 changed files with 34 additions and 25 deletions
|
@ -19,7 +19,7 @@ module.exports = {
|
||||||
// Allow only double quotes and backticks
|
// Allow only double quotes and backticks
|
||||||
quotes: ["error", "double"],
|
quotes: ["error", "double"],
|
||||||
// Warn if a line isn't indented with a multiple of 2
|
// Warn if a line isn't indented with a multiple of 2
|
||||||
indent: ["warn", 2],
|
indent: ["warn", 2, { "SwitchCase": 1 }],
|
||||||
// Don't enforce any particular brace style
|
// Don't enforce any particular brace style
|
||||||
curly: "off",
|
curly: "off",
|
||||||
// Allow only vars starting with _ to be ununsed vars
|
// Allow only vars starting with _ to be ununsed vars
|
||||||
|
@ -64,7 +64,7 @@ module.exports = {
|
||||||
"@typescript-eslint/quotes": ["error", "double"],
|
"@typescript-eslint/quotes": ["error", "double"],
|
||||||
semi: "off",
|
semi: "off",
|
||||||
"@typescript-eslint/semi": ["warn", "always"],
|
"@typescript-eslint/semi": ["warn", "always"],
|
||||||
indent: ["warn", 2],
|
indent: ["warn", 2, { "SwitchCase": 1 }],
|
||||||
"@typescript-eslint/indent": "off",
|
"@typescript-eslint/indent": "off",
|
||||||
"@typescript-eslint/comma-dangle": "warn",
|
"@typescript-eslint/comma-dangle": "warn",
|
||||||
"keyword-spacing": "off",
|
"keyword-spacing": "off",
|
||||||
|
|
|
@ -22,7 +22,7 @@ const queryClient = new QueryClient({
|
||||||
// delay = Math.min(1000 * 2 ** attemptIndex, 30000)
|
// delay = Math.min(1000 * 2 ** attemptIndex, 30000)
|
||||||
retry: true,
|
retry: true,
|
||||||
useErrorBoundary: true,
|
useErrorBoundary: true,
|
||||||
suspense: true,
|
suspense: true
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
@ -47,7 +47,7 @@ export function App() {
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
onReset={reset}
|
onReset={reset}
|
||||||
fallbackRender={ErrorPage}
|
FallbackComponent={ErrorPage}
|
||||||
>
|
>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<Portal>
|
<Portal>
|
||||||
|
|
|
@ -20,7 +20,7 @@ interface HttpConfig {
|
||||||
function encodeRFC3986URIComponent(str: string): string {
|
function encodeRFC3986URIComponent(str: string): string {
|
||||||
return encodeURIComponent(str).replace(
|
return encodeURIComponent(str).replace(
|
||||||
/[!'()*]/g,
|
/[!'()*]/g,
|
||||||
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,
|
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,27 +82,27 @@ export async function HttpClient<T = unknown>(
|
||||||
const response = await window.fetch(`${baseUrl()}${endpoint}`, init);
|
const response = await window.fetch(`${baseUrl()}${endpoint}`, init);
|
||||||
|
|
||||||
switch (response.status) {
|
switch (response.status) {
|
||||||
case 204:
|
case 204:
|
||||||
// 204 contains no data, but indicates success
|
// 204 contains no data, but indicates success
|
||||||
return Promise.resolve<T>({} as T);
|
return Promise.resolve<T>({} as T);
|
||||||
case 401:
|
case 401:
|
||||||
// Remove auth info from localStorage
|
// Remove auth info from localStorage
|
||||||
AuthContext.reset();
|
AuthContext.reset();
|
||||||
|
|
||||||
// Show an error toast to notify the user what occurred
|
// Show an error toast to notify the user what occurred
|
||||||
return Promise.reject(new Error(`[401] Unauthorized: "${endpoint}"`));
|
return Promise.reject(new Error(`[401] Unauthorized: "${endpoint}"`));
|
||||||
case 404:
|
case 404:
|
||||||
return Promise.reject(new Error(`[404] Not found: "${endpoint}"`));
|
return Promise.reject(new Error(`[404] Not found: "${endpoint}"`));
|
||||||
case 500:
|
case 500:
|
||||||
const health = await window.fetch(`${baseUrl()}api/healthz/liveness`);
|
const health = await window.fetch(`${baseUrl()}api/healthz/liveness`);
|
||||||
if (!health.ok) {
|
if (!health.ok) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error(`[500] Offline (Internal server error): "${endpoint}"`, { cause: "OFFLINE" })
|
new Error(`[500] Offline (Internal server error): "${endpoint}"`, { cause: "OFFLINE" })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isJson = response.headers.get("Content-Type")?.includes("application/json");
|
const isJson = response.headers.get("Content-Type")?.includes("application/json");
|
||||||
|
|
|
@ -17,11 +17,20 @@ export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const parseTitle = () => {
|
||||||
|
switch (error?.cause) {
|
||||||
|
case "OFFLINE":
|
||||||
|
return "Connection to Autobrr failed! Check the application state and verify your connectivity.";
|
||||||
|
default:
|
||||||
|
return "We caught an unrecoverable error!";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col justify-center py-12 px-2 sm:px-6 lg:px-8">
|
<div className="min-h-screen flex flex-col justify-center py-12 px-2 sm:px-6 lg:px-8">
|
||||||
<div className="sm:mx-auto sm:w-full sm:max-w-screen-md md:max-w-screen-lg lg:max-w-screen-xl">
|
<div className="sm:mx-auto sm:w-full sm:max-w-screen-md md:max-w-screen-lg lg:max-w-screen-xl">
|
||||||
<h1 className="text-3xl font-bold leading-6 text-gray-900 dark:text-gray-200 mt-4 mb-3">
|
<h1 className="text-3xl font-bold leading-6 text-gray-900 dark:text-gray-200 mt-4 mb-3">
|
||||||
We caught an unrecoverable error!
|
{parseTitle()}
|
||||||
</h1>
|
</h1>
|
||||||
<h3 className="text-xl leading-6 text-gray-700 dark:text-gray-400 mb-4">
|
<h3 className="text-xl leading-6 text-gray-700 dark:text-gray-400 mb-4">
|
||||||
Please consider reporting this error to our
|
Please consider reporting this error to our
|
||||||
|
@ -67,7 +76,7 @@ export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
|
||||||
</pre>
|
</pre>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="block text-gray-800 mb-2 text-md">
|
<span className="block text-gray-800 mb-2 text-md">
|
||||||
You can try resetting the page state using the button provided below.
|
You can try resetting the page state using the button provided below or restarting your autobrr application.
|
||||||
However, this is not guaranteed to fix the error.
|
However, this is not guaranteed to fix the error.
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue