diff --git a/src/locales/en.json b/src/locales/en.json
index 35579388..aa4b82e2 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -537,7 +537,9 @@
"remove": "Remove",
"revoke": "Revoke",
"create": "Create",
- "update": "Update"
+ "update": "Update",
+ "pageOutdated": "This page is outdated. Please refresh to load the latest version.",
+ "refreshPage": "Refresh Page"
},
"nav": {
"home": "Home",
diff --git a/src/ui/desktop/navigation/AppView.tsx b/src/ui/desktop/navigation/AppView.tsx
index e7d07e17..e077a82d 100644
--- a/src/ui/desktop/navigation/AppView.tsx
+++ b/src/ui/desktop/navigation/AppView.tsx
@@ -1,4 +1,6 @@
import React, {
+ Component,
+ type ErrorInfo,
Suspense,
lazy,
useEffect,
@@ -26,6 +28,61 @@ import { useTheme } from "@/components/theme-provider";
import { SimpleLoader } from "@/ui/desktop/navigation/animations/SimpleLoader.tsx";
import { useTranslation } from "react-i18next";
+class ChunkErrorBoundaryInner extends Component<
+ { children: React.ReactNode; message: string; buttonLabel: string },
+ { hasChunkError: boolean }
+> {
+ constructor(props: {
+ children: React.ReactNode;
+ message: string;
+ buttonLabel: string;
+ }) {
+ super(props);
+ this.state = { hasChunkError: false };
+ }
+
+ static getDerivedStateFromError(error: Error) {
+ const isChunkError =
+ error.name === "ChunkLoadError" ||
+ /Loading chunk \d+ failed/i.test(error.message) ||
+ /Failed to fetch dynamically imported module/i.test(error.message) ||
+ /error loading dynamically imported module/i.test(error.message);
+ if (isChunkError) return { hasChunkError: true };
+ throw error;
+ }
+
+ componentDidCatch(_error: Error, _info: ErrorInfo) {}
+
+ render() {
+ if (this.state.hasChunkError) {
+ return (
+
+ {this.props.message}
+
+
+ );
+ }
+ return this.props.children;
+ }
+}
+
+function ChunkErrorBoundary({ children }: { children: React.ReactNode }) {
+ const { t } = useTranslation();
+ return (
+
+ {children}
+
+ );
+}
+
const Terminal = lazy(() =>
import("@/ui/desktop/apps/features/terminal/Terminal.tsx").then((module) => ({
default: module.Terminal,
@@ -449,111 +506,113 @@ export function AppView({
: "var(--bg-base)",
}}
>
-
- }
- >
- {t.type === "terminal" ? (
- 0}
- onClose={() => removeTab(t.id)}
- onTitleChange={(title) => updateTab(t.id, { title })}
- onOpenFileManager={
- (t.hostConfig as any)?.enableFileManager
- ? (path?: string) =>
- addTab({
- type: "file_manager",
- title: t.title,
- hostConfig: path
- ? {
- ...t.hostConfig!,
- defaultPath: path,
- }
- : t.hostConfig,
- })
- : undefined
- }
- previewTheme={
- t.id === currentTab ? previewTerminalTheme : null
- }
- />
- ) : t.type === "server_stats" ? (
-
- ) : t.type === "rdp" ||
- t.type === "vnc" ||
- t.type === "telnet" ? (
- t.connectionConfig ? (
-
+
+ }
+ >
+ {t.type === "terminal" ? (
+ removeTab(t.id)}
- onError={(err) => {
- toast.error(err);
- removeTab(t.id);
- }}
+ title={t.title}
+ showTitle={false}
+ splitScreen={allSplitScreenTab.length > 0}
+ onClose={() => removeTab(t.id)}
+ onTitleChange={(title) => updateTab(t.id, { title })}
+ onOpenFileManager={
+ (t.hostConfig as any)?.enableFileManager
+ ? (path?: string) =>
+ addTab({
+ type: "file_manager",
+ title: t.title,
+ hostConfig: path
+ ? {
+ ...t.hostConfig!,
+ defaultPath: path,
+ }
+ : t.hostConfig,
+ })
+ : undefined
+ }
+ previewTheme={
+ t.id === currentTab ? previewTerminalTheme : null
+ }
+ />
+ ) : t.type === "server_stats" ? (
+
+ ) : t.type === "rdp" ||
+ t.type === "vnc" ||
+ t.type === "telnet" ? (
+ t.connectionConfig ? (
+ removeTab(t.id)}
+ onError={(err) => {
+ toast.error(err);
+ removeTab(t.id);
+ }}
+ />
+ ) : (
+
+ Missing connection configuration
+
+ )
+ ) : t.type === "network_graph" ? (
+
+ ) : t.type === "tunnel" ? (
+
+ ) : t.type === "docker" ? (
+ removeTab(t.id)}
/>
) : (
-
- Missing connection configuration
-
- )
- ) : t.type === "network_graph" ? (
-
- ) : t.type === "tunnel" ? (
-
- ) : t.type === "docker" ? (
- removeTab(t.id)}
- />
- ) : (
- removeTab(t.id)}
- />
- )}
-
+ removeTab(t.id)}
+ />
+ )}
+
+
);