mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-15 21:03:47 +00:00
fix: remote desktop blank screen
This commit is contained in:
+3
-1
@@ -537,7 +537,9 @@
|
|||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"revoke": "Revoke",
|
"revoke": "Revoke",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"update": "Update"
|
"update": "Update",
|
||||||
|
"pageOutdated": "This page is outdated. Please refresh to load the latest version.",
|
||||||
|
"refreshPage": "Refresh Page"
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import React, {
|
import React, {
|
||||||
|
Component,
|
||||||
|
type ErrorInfo,
|
||||||
Suspense,
|
Suspense,
|
||||||
lazy,
|
lazy,
|
||||||
useEffect,
|
useEffect,
|
||||||
@@ -26,6 +28,61 @@ import { useTheme } from "@/components/theme-provider";
|
|||||||
import { SimpleLoader } from "@/ui/desktop/navigation/animations/SimpleLoader.tsx";
|
import { SimpleLoader } from "@/ui/desktop/navigation/animations/SimpleLoader.tsx";
|
||||||
import { useTranslation } from "react-i18next";
|
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 (
|
||||||
|
<div className="flex flex-col items-center justify-center h-full gap-3 text-sm text-[var(--text-secondary)]">
|
||||||
|
<span>{this.props.message}</span>
|
||||||
|
<button
|
||||||
|
className="px-3 py-1.5 rounded bg-[var(--bg-secondary)] hover:bg-[var(--bg-tertiary)] text-[var(--text-primary)] transition-colors"
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
>
|
||||||
|
{this.props.buttonLabel}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChunkErrorBoundary({ children }: { children: React.ReactNode }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<ChunkErrorBoundaryInner
|
||||||
|
message={t("common.pageOutdated")}
|
||||||
|
buttonLabel={t("common.refreshPage")}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ChunkErrorBoundaryInner>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const Terminal = lazy(() =>
|
const Terminal = lazy(() =>
|
||||||
import("@/ui/desktop/apps/features/terminal/Terminal.tsx").then((module) => ({
|
import("@/ui/desktop/apps/features/terminal/Terminal.tsx").then((module) => ({
|
||||||
default: module.Terminal,
|
default: module.Terminal,
|
||||||
@@ -449,111 +506,113 @@ export function AppView({
|
|||||||
: "var(--bg-base)",
|
: "var(--bg-base)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Suspense
|
<ChunkErrorBoundary>
|
||||||
fallback={
|
<Suspense
|
||||||
<SimpleLoader
|
fallback={
|
||||||
visible={true}
|
<SimpleLoader
|
||||||
message={translate("common.loading")}
|
visible={true}
|
||||||
backgroundColor={
|
message={translate("common.loading")}
|
||||||
isTerminal ? backgroundColor : "var(--bg-base)"
|
backgroundColor={
|
||||||
}
|
isTerminal ? backgroundColor : "var(--bg-base)"
|
||||||
/>
|
}
|
||||||
}
|
/>
|
||||||
>
|
}
|
||||||
{t.type === "terminal" ? (
|
>
|
||||||
<Terminal
|
{t.type === "terminal" ? (
|
||||||
key={`term-${t.id}-${t.instanceId || ""}`}
|
<Terminal
|
||||||
ref={t.terminalRef}
|
key={`term-${t.id}-${t.instanceId || ""}`}
|
||||||
hostConfig={t.hostConfig}
|
ref={t.terminalRef}
|
||||||
isVisible={effectiveVisible}
|
hostConfig={t.hostConfig}
|
||||||
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" ? (
|
|
||||||
<ServerView
|
|
||||||
key={`stats-${t.id}-${t.instanceId || ""}`}
|
|
||||||
hostConfig={t.hostConfig}
|
|
||||||
title={t.title}
|
|
||||||
isVisible={effectiveVisible}
|
|
||||||
isTopbarOpen={isTopbarOpen}
|
|
||||||
embedded
|
|
||||||
/>
|
|
||||||
) : t.type === "rdp" ||
|
|
||||||
t.type === "vnc" ||
|
|
||||||
t.type === "telnet" ? (
|
|
||||||
t.connectionConfig ? (
|
|
||||||
<GuacamoleDisplay
|
|
||||||
key={`guac-${t.id}-${t.instanceId || ""}`}
|
|
||||||
connectionConfig={t.connectionConfig}
|
|
||||||
isVisible={effectiveVisible}
|
isVisible={effectiveVisible}
|
||||||
onDisconnect={() => removeTab(t.id)}
|
title={t.title}
|
||||||
onError={(err) => {
|
showTitle={false}
|
||||||
toast.error(err);
|
splitScreen={allSplitScreenTab.length > 0}
|
||||||
removeTab(t.id);
|
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" ? (
|
||||||
|
<ServerView
|
||||||
|
key={`stats-${t.id}-${t.instanceId || ""}`}
|
||||||
|
hostConfig={t.hostConfig}
|
||||||
|
title={t.title}
|
||||||
|
isVisible={effectiveVisible}
|
||||||
|
isTopbarOpen={isTopbarOpen}
|
||||||
|
embedded
|
||||||
|
/>
|
||||||
|
) : t.type === "rdp" ||
|
||||||
|
t.type === "vnc" ||
|
||||||
|
t.type === "telnet" ? (
|
||||||
|
t.connectionConfig ? (
|
||||||
|
<GuacamoleDisplay
|
||||||
|
key={`guac-${t.id}-${t.instanceId || ""}`}
|
||||||
|
connectionConfig={t.connectionConfig}
|
||||||
|
isVisible={effectiveVisible}
|
||||||
|
onDisconnect={() => removeTab(t.id)}
|
||||||
|
onError={(err) => {
|
||||||
|
toast.error(err);
|
||||||
|
removeTab(t.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center h-full text-red-500">
|
||||||
|
Missing connection configuration
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
) : t.type === "network_graph" ? (
|
||||||
|
<NetworkGraphCard
|
||||||
|
key={`netgraph-${t.id}-${t.instanceId || ""}`}
|
||||||
|
isTopbarOpen={isTopbarOpen}
|
||||||
|
rightSidebarOpen={rightSidebarOpen}
|
||||||
|
rightSidebarWidth={rightSidebarWidth}
|
||||||
|
embedded={false}
|
||||||
|
/>
|
||||||
|
) : t.type === "tunnel" ? (
|
||||||
|
<TunnelManager
|
||||||
|
key={`tunnel-${t.id}-${t.instanceId || ""}`}
|
||||||
|
hostConfig={t.hostConfig}
|
||||||
|
title={t.title}
|
||||||
|
isVisible={effectiveVisible}
|
||||||
|
isTopbarOpen={isTopbarOpen}
|
||||||
|
embedded
|
||||||
|
/>
|
||||||
|
) : t.type === "docker" ? (
|
||||||
|
<DockerManager
|
||||||
|
key={`docker-${t.id}-${t.instanceId || ""}`}
|
||||||
|
hostConfig={t.hostConfig}
|
||||||
|
title={t.title}
|
||||||
|
isVisible={effectiveVisible}
|
||||||
|
isTopbarOpen={isTopbarOpen}
|
||||||
|
embedded
|
||||||
|
onClose={() => removeTab(t.id)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-full text-red-500">
|
<FileManager
|
||||||
Missing connection configuration
|
key={`filemgr-${t.id}-${t.instanceId || ""}`}
|
||||||
</div>
|
embedded
|
||||||
)
|
initialHost={t.hostConfig}
|
||||||
) : t.type === "network_graph" ? (
|
onClose={() => removeTab(t.id)}
|
||||||
<NetworkGraphCard
|
/>
|
||||||
key={`netgraph-${t.id}-${t.instanceId || ""}`}
|
)}
|
||||||
isTopbarOpen={isTopbarOpen}
|
</Suspense>
|
||||||
rightSidebarOpen={rightSidebarOpen}
|
</ChunkErrorBoundary>
|
||||||
rightSidebarWidth={rightSidebarWidth}
|
|
||||||
embedded={false}
|
|
||||||
/>
|
|
||||||
) : t.type === "tunnel" ? (
|
|
||||||
<TunnelManager
|
|
||||||
key={`tunnel-${t.id}-${t.instanceId || ""}`}
|
|
||||||
hostConfig={t.hostConfig}
|
|
||||||
title={t.title}
|
|
||||||
isVisible={effectiveVisible}
|
|
||||||
isTopbarOpen={isTopbarOpen}
|
|
||||||
embedded
|
|
||||||
/>
|
|
||||||
) : t.type === "docker" ? (
|
|
||||||
<DockerManager
|
|
||||||
key={`docker-${t.id}-${t.instanceId || ""}`}
|
|
||||||
hostConfig={t.hostConfig}
|
|
||||||
title={t.title}
|
|
||||||
isVisible={effectiveVisible}
|
|
||||||
isTopbarOpen={isTopbarOpen}
|
|
||||||
embedded
|
|
||||||
onClose={() => removeTab(t.id)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<FileManager
|
|
||||||
key={`filemgr-${t.id}-${t.instanceId || ""}`}
|
|
||||||
embedded
|
|
||||||
initialHost={t.hostConfig}
|
|
||||||
onClose={() => removeTab(t.id)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Suspense>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user