mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-16 05:13:36 +00:00
perf: frontend request cache, poll pause, and code-split shell (#1052)
Host/status caching, shell code-split, SSH pool waits, host-metrics concurrency, background-tab idle, per-host status subscriptions, homepage poll quieting, and virtualized host sidebar + file manager lists.
This commit is contained in:
+106
-34
@@ -15,32 +15,86 @@ import {
|
||||
TerminalSquare,
|
||||
Layers, // --- tmux-monitor ---
|
||||
} from "lucide-react";
|
||||
import { lazy, Suspense } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CommandHistoryProvider } from "@/features/terminal/command-history/CommandHistoryContext";
|
||||
import { Serial } from "@/features/serial/Serial";
|
||||
import type { SerialHandle } from "@/features/serial/serial-types";
|
||||
import { Terminal as TerminalFeature } from "@/features/terminal/Terminal";
|
||||
import type {
|
||||
TerminalHandle,
|
||||
TerminalHostConfig,
|
||||
} from "@/features/terminal/Terminal";
|
||||
import { MobileTerminalKeyboard } from "@/features/terminal/MobileTerminalKeyboard";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { FileManager } from "@/features/file-manager/FileManager";
|
||||
import { DockerManager } from "@/features/docker/DockerManager";
|
||||
import { HostMetricsTab } from "@/features/host-metrics/HostMetricsTab";
|
||||
// --- tmux-monitor ---
|
||||
import { TmuxMonitor } from "@/features/tmux-monitor/TmuxMonitor";
|
||||
import GuacamoleApp from "@/features/guacamole/GuacamoleApp";
|
||||
import type { GuacamoleAppHandle } from "@/features/guacamole/GuacamoleApp";
|
||||
import { DashboardTab } from "@/dashboard/DashboardTab";
|
||||
import { HomepageCanvas } from "@/features/homepage/HomepageCanvas";
|
||||
import { TunnelTab } from "@/features/tunnel/TunnelTab";
|
||||
import { NetworkGraphCard } from "@/dashboard/cards/NetworkGraphCard";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import type { Tab, TabType, Host } from "@/types/ui-types";
|
||||
import type { SSHHost } from "@/types";
|
||||
import { useTabsSafe } from "@/shell/TabContext";
|
||||
|
||||
// Heavy tab surfaces — keep out of the AppShell critical path.
|
||||
const CommandHistoryProvider = lazy(() =>
|
||||
import("@/features/terminal/command-history/CommandHistoryContext").then(
|
||||
(m) => ({ default: m.CommandHistoryProvider }),
|
||||
),
|
||||
);
|
||||
const TerminalFeature = lazy(() =>
|
||||
import("@/features/terminal/Terminal").then((m) => ({
|
||||
default: m.Terminal,
|
||||
})),
|
||||
);
|
||||
const MobileTerminalKeyboard = lazy(() =>
|
||||
import("@/features/terminal/MobileTerminalKeyboard").then((m) => ({
|
||||
default: m.MobileTerminalKeyboard,
|
||||
})),
|
||||
);
|
||||
const FileManager = lazy(() =>
|
||||
import("@/features/file-manager/FileManager").then((m) => ({
|
||||
default: m.FileManager,
|
||||
})),
|
||||
);
|
||||
const DockerManager = lazy(() =>
|
||||
import("@/features/docker/DockerManager").then((m) => ({
|
||||
default: m.DockerManager,
|
||||
})),
|
||||
);
|
||||
const HostMetricsTab = lazy(() =>
|
||||
import("@/features/host-metrics/HostMetricsTab").then((m) => ({
|
||||
default: m.HostMetricsTab,
|
||||
})),
|
||||
);
|
||||
const TmuxMonitor = lazy(() =>
|
||||
import("@/features/tmux-monitor/TmuxMonitor").then((m) => ({
|
||||
default: m.TmuxMonitor,
|
||||
})),
|
||||
);
|
||||
const GuacamoleApp = lazy(() =>
|
||||
import("@/features/guacamole/GuacamoleApp").then((m) => ({
|
||||
default: m.default,
|
||||
})),
|
||||
);
|
||||
const DashboardTab = lazy(() =>
|
||||
import("@/dashboard/DashboardTab").then((m) => ({
|
||||
default: m.DashboardTab,
|
||||
})),
|
||||
);
|
||||
const HomepageCanvas = lazy(() =>
|
||||
import("@/features/homepage/HomepageCanvas").then((m) => ({
|
||||
default: m.HomepageCanvas,
|
||||
})),
|
||||
);
|
||||
const TunnelTab = lazy(() =>
|
||||
import("@/features/tunnel/TunnelTab").then((m) => ({
|
||||
default: m.TunnelTab,
|
||||
})),
|
||||
);
|
||||
const NetworkGraphCard = lazy(() =>
|
||||
import("@/dashboard/cards/NetworkGraphCard").then((m) => ({
|
||||
default: m.NetworkGraphCard,
|
||||
})),
|
||||
);
|
||||
const Serial = lazy(() =>
|
||||
import("@/features/serial/Serial").then((m) => ({
|
||||
default: m.Serial,
|
||||
})),
|
||||
);
|
||||
|
||||
function hostToSSHHost(h: Host): SSHHost {
|
||||
return {
|
||||
id: parseInt(h.id, 10),
|
||||
@@ -95,6 +149,18 @@ function EmptyState({
|
||||
);
|
||||
}
|
||||
|
||||
function TabChunkFallback() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center bg-background">
|
||||
<div className="size-5 rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground/70 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function withTabSuspense(node: React.ReactNode) {
|
||||
return <Suspense fallback={<TabChunkFallback />}>{node}</Suspense>;
|
||||
}
|
||||
|
||||
export function tabIcon(type: TabType) {
|
||||
switch (type) {
|
||||
case "dashboard":
|
||||
@@ -154,7 +220,7 @@ function TerminalTabContent({
|
||||
}) {
|
||||
const { previewTerminalTheme } = useTabsSafe();
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
return withTabSuspense(
|
||||
<CommandHistoryProvider>
|
||||
<div className="flex flex-col h-full w-full">
|
||||
<div className="flex-1 min-h-0">
|
||||
@@ -192,7 +258,7 @@ function TerminalTabContent({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</CommandHistoryProvider>
|
||||
</CommandHistoryProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -211,11 +277,12 @@ export function renderTabContent(
|
||||
|
||||
switch (tab.type) {
|
||||
case "dashboard":
|
||||
return (
|
||||
return withTabSuspense(
|
||||
<DashboardTab
|
||||
onOpenSingletonTab={onOpenSingletonTab!}
|
||||
onOpenTab={onOpenTab!}
|
||||
/>
|
||||
isVisible={isVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
case "terminal":
|
||||
@@ -253,29 +320,30 @@ export function renderTabContent(
|
||||
messageKey="fileManager.noHostSelected"
|
||||
/>
|
||||
);
|
||||
return (
|
||||
return withTabSuspense(
|
||||
<FileManager
|
||||
initialHost={hostToSSHHost(host)}
|
||||
initialFilePath={tab.initialFilePath}
|
||||
isVisible={isVisible}
|
||||
onOpenTerminalTab={
|
||||
onOpenTerminalTab
|
||||
? (path) => onOpenTerminalTab(host, path)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
case "docker":
|
||||
if (!host)
|
||||
return <EmptyState icon={Box} messageKey="docker.noHostSelected" />;
|
||||
return (
|
||||
return withTabSuspense(
|
||||
<DockerManager
|
||||
hostConfig={hostToSSHHost(host)}
|
||||
title={label}
|
||||
isVisible={isVisible}
|
||||
isTopbarOpen={false}
|
||||
embedded={true}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
case "host-metrics":
|
||||
@@ -283,18 +351,20 @@ export function renderTabContent(
|
||||
return (
|
||||
<EmptyState icon={Activity} messageKey="hostMetrics.noHostSelected" />
|
||||
);
|
||||
return (
|
||||
return withTabSuspense(
|
||||
<HostMetricsTab
|
||||
hostConfig={hostToSSHHost(host)}
|
||||
title={label}
|
||||
isVisible={isVisible}
|
||||
isTopbarOpen={false}
|
||||
embedded={true}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
case "tunnel":
|
||||
return <TunnelTab label={label} host={host} />;
|
||||
return withTabSuspense(
|
||||
<TunnelTab label={label} host={host} isVisible={isVisible} />,
|
||||
);
|
||||
|
||||
case "rdp":
|
||||
case "vnc":
|
||||
@@ -303,41 +373,43 @@ export function renderTabContent(
|
||||
return (
|
||||
<EmptyState icon={Monitor} messageKey="guacamole.noHostSelected" />
|
||||
);
|
||||
return (
|
||||
return withTabSuspense(
|
||||
<GuacamoleApp
|
||||
ref={tab.terminalRef as React.Ref<GuacamoleAppHandle>}
|
||||
hostId={host.id}
|
||||
tabId={tab.id}
|
||||
protocol={tab.type as "rdp" | "vnc" | "telnet"}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
case "network_graph":
|
||||
return <NetworkGraphCard embedded={false} />;
|
||||
return withTabSuspense(
|
||||
<NetworkGraphCard embedded={false} isVisible={isVisible} />,
|
||||
);
|
||||
|
||||
// --- tmux-monitor ---
|
||||
case "tmux_monitor":
|
||||
return (
|
||||
return withTabSuspense(
|
||||
<TmuxMonitor
|
||||
initialHostId={host ? parseInt(host.id, 10) : undefined}
|
||||
isVisible={isVisible}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
case "serial":
|
||||
if (!tab.serialConfig)
|
||||
return <EmptyState icon={Usb} messageKey="serial.notSupportedTitle" />;
|
||||
return (
|
||||
return withTabSuspense(
|
||||
<Serial
|
||||
ref={tab.terminalRef as React.Ref<SerialHandle>}
|
||||
config={tab.serialConfig}
|
||||
isVisible={isVisible}
|
||||
instanceId={tab.instanceId}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
case "homepage":
|
||||
return <HomepageCanvas />;
|
||||
return withTabSuspense(<HomepageCanvas />);
|
||||
|
||||
case "host-manager":
|
||||
case "user-profile":
|
||||
|
||||
Reference in New Issue
Block a user