fix(frontend): 终端弹框适配与审计实时监控布局优化
- 修复 LiveMonitorDialog 连接中卡死:WebSocket 先连、延后挂载 xterm - 审计实时监控弹框对齐 WebSSH 布局:92vw 宽、520px 高、支持全屏 - 修复服务器终端弹框右侧黑边:fit 后同步 PTY resize,延迟二次 fit - 抽取 xterm-utils 公共适配逻辑,统一 ResizeObserver 处理
This commit is contained in:
1 parent
be2a377f01
commit
bfd6431b9d
4 files changed
+228
-126
No files matched your search
@@ -11,6 +11,7 @@ import { FitAddon } from 'xterm-addon-fit';
|
||||
import 'xterm/css/xterm.css';
|
||||
import { Maximize2, Minimize2, X, Shield} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { attachTerminalResize, fitTerminal, mountXterm } from '@/lib/xterm-utils';
|
||||
import { useUserStore } from '@/store';
|
||||
import { credentialApi, serverCredentialApi } from '@/services/api';
|
||||
import { toast } from 'sonner';
|
||||
@@ -127,15 +128,8 @@ export function TerminalDialog({
|
||||
|
||||
ws.onopen = () => {
|
||||
setConnected(true);
|
||||
if (containerRef.current) {
|
||||
term.open(containerRef.current);
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
fit.fit();
|
||||
term.focus();
|
||||
} catch { /* noop */ }
|
||||
});
|
||||
}
|
||||
// 挂载后 fit + 同步 PTY 尺寸,避免右侧黑边 // Logan.Li
|
||||
mountXterm(term, fit, () => containerRef.current, ws);
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
@@ -158,39 +152,33 @@ export function TerminalDialog({
|
||||
};
|
||||
}, [open, serverId, authReady]);
|
||||
|
||||
// 用 ResizeObserver 监听容器实际尺寸变化,自动 fit 终端
|
||||
// 这比 window resize 或 setTimeout 更可靠:
|
||||
// - 全屏/窗口切换时容器尺寸在 CSS 过渡期间持续变化,ResizeObserver 每次都会触发
|
||||
// - 不需要猜测延迟时间
|
||||
// 容器尺寸变化时 fit 并同步远端 PTY // Logan.Li
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
if (!open || !authReady) return;
|
||||
|
||||
let resizeTimer: ReturnType<typeof setTimeout>;
|
||||
const ro = new ResizeObserver(() => {
|
||||
// 防抖:CSS 过渡期间会连续触发多次,合并为最后一次
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(() => {
|
||||
if (fitRef.current && termRef.current && wsRef.current) {
|
||||
try {
|
||||
fitRef.current.fit();
|
||||
termRef.current.focus();
|
||||
const { cols, rows } = termRef.current;
|
||||
if (wsRef.current.readyState === WebSocket.OPEN) {
|
||||
wsRef.current.send(JSON.stringify({ status: 1, cols, rows }));
|
||||
}
|
||||
} catch { /* noop */ }
|
||||
}
|
||||
}, 60);
|
||||
});
|
||||
let cleanup: (() => void) | undefined;
|
||||
let retry = 0;
|
||||
|
||||
ro.observe(container);
|
||||
return () => {
|
||||
clearTimeout(resizeTimer);
|
||||
ro.disconnect();
|
||||
const attach = () => {
|
||||
const container = containerRef.current;
|
||||
const fit = fitRef.current;
|
||||
const term = termRef.current;
|
||||
const ws = wsRef.current;
|
||||
if (!container || !fit || !term) {
|
||||
if (retry++ < 30) requestAnimationFrame(attach);
|
||||
return;
|
||||
}
|
||||
cleanup = attachTerminalResize(container, fit, term, ws);
|
||||
try {
|
||||
fitTerminal(fit, term, ws);
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
attach();
|
||||
return () => cleanup?.();
|
||||
}, [open, authReady, fullscreen]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
@@ -204,7 +192,7 @@ export function TerminalDialog({
|
||||
? '!max-w-none !w-screen !h-screen !rounded-none !translate-x-0 !translate-y-0 !left-0 !top-0'
|
||||
: ''
|
||||
)}
|
||||
style={fullscreen ? { maxWidth: '100vw', width: '100vw', height: '100vh' } : { maxWidth: '900px', width: '90vw' }}
|
||||
style={fullscreen ? { maxWidth: '100vw', width: '100vw', height: '100vh' } : { maxWidth: '960px', width: '92vw' }}
|
||||
>
|
||||
<DialogTitle className="sr-only">终端 - {serverName}</DialogTitle>
|
||||
<DialogDescription className="sr-only">SSH 终端连接</DialogDescription>
|
||||
@@ -267,8 +255,8 @@ export function TerminalDialog({
|
||||
{authReady && (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="bg-[#0a0e14]"
|
||||
style={{ height: fullscreen ? 'calc(100vh - 49px)' : '480px' }}
|
||||
className="xterm-terminal-host"
|
||||
style={{ height: fullscreen ? 'calc(100vh - 49px)' : '520px' }}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
Reference in new issue
Block a user