fix(frontend): 终端窗口壳问题修复

- 详情抽屉打开终端/RDP/文件浏览前先关闭抽屉,修复同 z-index 遮挡冲突
- 删除语义不通的黄灯按钮,绿灯随全屏状态切换 aria-label
- 连接指示点补充 role=status 与 sr-only 文案,不再仅靠颜色区分
- LiveMonitorDialog 接入 i18n;修复 TerminalDialog 乱码注释;全屏文案迁至 common 命名空间
This commit is contained in:
Logan.Li committed 2026-09-14 15:28:15 +08:00
1 parent 115e8b451c
commit 0563a29de1
4 files changed
+46 -29

No files matched your search

@@ -1,4 +1,5 @@
// macOS 风格终端标题栏:红黄绿 + user@host:~ // Logan.Li
// macOS 风格终端标题栏:红绿 + user@host:~。终端为固定深色主题,
// 此处的颜色常量是终端专属暗色 token,不跟随全局主题(豁免 var(--dg-*))。 // Logan.Li
import type { ReactNode } from 'react';
import { cn } from '@/lib/utils';
@@ -23,6 +24,9 @@ interface TerminalWindowChromeProps {
closeLabel: string;
fullscreenLabel: string;
restoreLabel: string;
/** 连接状态的无障碍文案,与红/绿指示点配套,避免仅靠颜色区分 // Logan.Li */
connectedLabel?: string;
disconnectedLabel?: string;
}
export function TerminalWindowChrome({
@@ -35,7 +39,10 @@ export function TerminalWindowChrome({
closeLabel,
fullscreenLabel,
restoreLabel,
connectedLabel,
disconnectedLabel,
}: TerminalWindowChromeProps) {
// 弹窗没有“最小化”语义,只保留红(关闭)绿(全屏切换)两盏灯,去掉误导性黄灯。 // Logan.Li
return (
<div
className="flex items-center gap-2 border-b px-4 py-2.5"
@@ -50,19 +57,8 @@ export function TerminalWindowChrome({
/>
<button
type="button"
aria-label={restoreLabel}
title={restoreLabel}
disabled={!fullscreen}
onClick={() => fullscreen && onToggleFullscreen()}
className={cn(
'h-3 w-3 shrink-0 rounded-full bg-[#febc2e]',
fullscreen ? 'hover:brightness-110' : 'opacity-70',
)}
/>
<button
type="button"
aria-label={fullscreenLabel}
title={fullscreenLabel}
aria-label={fullscreen ? restoreLabel : fullscreenLabel}
title={fullscreen ? restoreLabel : fullscreenLabel}
onClick={onToggleFullscreen}
className="h-3 w-3 shrink-0 rounded-full bg-[#28c840] hover:brightness-110"
/>
@@ -71,11 +67,20 @@ export function TerminalWindowChrome({
</span>
{connected != null && (
<span
className={cn(
'h-1.5 w-1.5 shrink-0 rounded-full',
connected ? 'bg-[#28c840]' : 'bg-[#ff5f57]',
role="status"
title={connected ? connectedLabel : disconnectedLabel}
className="flex shrink-0 items-center gap-1"
>
<span
className={cn(
'h-1.5 w-1.5 rounded-full',
connected ? 'bg-[#28c840]' : 'bg-[#ff5f57]',
)}
/>
{(connected ? connectedLabel : disconnectedLabel) && (
<span className="sr-only">{connected ? connectedLabel : disconnectedLabel}</span>
)}
/>
</span>
)}
{trailing && <div className="ml-auto flex min-w-0 items-center gap-1">{trailing}</div>}
</div>
@@ -128,7 +128,11 @@ export function ServerManager() {
const getServerDisplayName = (server: JsonData) =>
server.alias || server.cloud_instance_name || server.hostname || server.ip || "";
// 详情抽屉内打开终端/RDP/文件时先关闭抽屉,避免同 z-index 的 Sheet 遮挡弹窗 // Logan.Li
const closeDetailSheet = () => setDetailServer(null);
const openTerminal = (server: JsonData) => {
closeDetailSheet();
setTerminalServer({
id: server.id,
name: getServerDisplayName(server),
@@ -140,11 +144,13 @@ export function ServerManager() {
};
const openRdp = (server: JsonData) => {
closeDetailSheet();
setRdpServer({ id: server.id, name: getServerDisplayName(server), ip: server.ip });
setRdpOpen(true);
};
const openFileBrowser = (server: JsonData) => {
closeDetailSheet();
setFileBrowserServer({ id: server.id, name: getServerDisplayName(server) });
setFileBrowserOpen(true);
};
@@ -38,7 +38,7 @@ interface TerminalDialogProps {
serverName: string;
serverIp: string;
username?: string;
/** ????????????????? SSH ??? // Logan.Li */
/** 资产是否已配置 SSH 凭据,用于终端连接前的判断 // Logan.Li */
hasAuth?: boolean;
}
@@ -227,8 +227,10 @@ export function TerminalDialog({
onToggleFullscreen={() => setFullscreen(!fullscreen)}
onClose={() => onOpenChange(false)}
closeLabel={t("common.close")}
fullscreenLabel={t("server.detailSheet.fullscreen")}
restoreLabel={t("server.detailSheet.exitFullscreen")}
fullscreenLabel={t("common.fullscreen")}
restoreLabel={t("common.exitFullscreen")}
connectedLabel={t("common.connected")}
disconnectedLabel={t("common.disconnected")}
/>
{!authReady && (
<div
@@ -11,6 +11,7 @@ import {
} from '@/components/ui/dialog';
import { Badge } from '@/components/ui/badge';
import { Loader2 } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { cn } from '@/lib/utils';
import {
TerminalWindowChrome,
@@ -40,6 +41,7 @@ interface LiveMonitorProps {
}
export function LiveMonitorDialog({ open, onOpenChange, sessionId, sessionInfo }: LiveMonitorProps) {
const { t } = useTranslation();
const containerRef = useRef<HTMLDivElement>(null);
const termRef = useRef<XTerm | null>(null);
const fitRef = useRef<FitAddon | null>(null);
@@ -198,17 +200,19 @@ export function LiveMonitorDialog({ open, onOpenChange, sessionId, sessionInfo }
: { maxWidth: '960px', width: '92vw' }),
}}
>
<DialogTitle className="sr-only">终端实时监控</DialogTitle>
<DialogDescription className="sr-only">审计在线 WebSSH 会话</DialogDescription>
<DialogTitle className="sr-only">{t('system.liveMonitor.title')}</DialogTitle>
<DialogDescription className="sr-only">{t('system.liveMonitor.description')}</DialogDescription>
<TerminalWindowChrome
sessionTitle={`${meta?.username || 'monitor'}@${meta?.server_name || 'host'}:~`}
connected={connected}
fullscreen={fullscreen}
onToggleFullscreen={() => setFullscreen(!fullscreen)}
onClose={() => onOpenChange(false)}
closeLabel="关闭"
fullscreenLabel="全屏"
restoreLabel="退出全屏"
closeLabel={t('common.close')}
fullscreenLabel={t('common.fullscreen')}
restoreLabel={t('common.exitFullscreen')}
connectedLabel={t('common.connected')}
disconnectedLabel={t('common.disconnected')}
trailing={
<>
{meta && (
@@ -218,18 +222,18 @@ export function LiveMonitorDialog({ open, onOpenChange, sessionId, sessionInfo }
)}
{connected && (
<Badge variant="outline" className="border-emerald-500/30 bg-emerald-500/10 text-xs text-emerald-400">
在线
{t('common.online')}
</Badge>
)}
{!connected && !connectError && (
<Badge variant="outline" className="border-yellow-500/30 bg-yellow-500/10 text-xs text-yellow-400">
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
连接中
{t('system.liveMonitor.connecting')}
</Badge>
)}
{connectError && (
<Badge variant="outline" className="border-red-500/30 bg-red-500/10 text-xs text-red-400">
连接失败
{t('system.liveMonitor.connectFailed')}
</Badge>
)}
</>