fix(frontend): 终端窗口壳问题修复
- 详情抽屉打开终端/RDP/文件浏览前先关闭抽屉,修复同 z-index 遮挡冲突 - 删除语义不通的黄灯按钮,绿灯随全屏状态切换 aria-label - 连接指示点补充 role=status 与 sr-only 文案,不再仅靠颜色区分 - LiveMonitorDialog 接入 i18n;修复 TerminalDialog 乱码注释;全屏文案迁至 common 命名空间
This commit is contained in:
1 parent
115e8b451c
commit
0563a29de1
4 files changed
+43
-26
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 type { ReactNode } from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
@@ -23,6 +24,9 @@ interface TerminalWindowChromeProps {
|
|||||||
closeLabel: string;
|
closeLabel: string;
|
||||||
fullscreenLabel: string;
|
fullscreenLabel: string;
|
||||||
restoreLabel: string;
|
restoreLabel: string;
|
||||||
|
/** 连接状态的无障碍文案,与红/绿指示点配套,避免仅靠颜色区分 // Logan.Li */
|
||||||
|
connectedLabel?: string;
|
||||||
|
disconnectedLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TerminalWindowChrome({
|
export function TerminalWindowChrome({
|
||||||
@@ -35,7 +39,10 @@ export function TerminalWindowChrome({
|
|||||||
closeLabel,
|
closeLabel,
|
||||||
fullscreenLabel,
|
fullscreenLabel,
|
||||||
restoreLabel,
|
restoreLabel,
|
||||||
|
connectedLabel,
|
||||||
|
disconnectedLabel,
|
||||||
}: TerminalWindowChromeProps) {
|
}: TerminalWindowChromeProps) {
|
||||||
|
// 弹窗没有“最小化”语义,只保留红(关闭)绿(全屏切换)两盏灯,去掉误导性黄灯。 // Logan.Li
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-2 border-b px-4 py-2.5"
|
className="flex items-center gap-2 border-b px-4 py-2.5"
|
||||||
@@ -50,19 +57,8 @@ export function TerminalWindowChrome({
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={restoreLabel}
|
aria-label={fullscreen ? restoreLabel : fullscreenLabel}
|
||||||
title={restoreLabel}
|
title={fullscreen ? restoreLabel : fullscreenLabel}
|
||||||
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}
|
|
||||||
onClick={onToggleFullscreen}
|
onClick={onToggleFullscreen}
|
||||||
className="h-3 w-3 shrink-0 rounded-full bg-[#28c840] hover:brightness-110"
|
className="h-3 w-3 shrink-0 rounded-full bg-[#28c840] hover:brightness-110"
|
||||||
/>
|
/>
|
||||||
@@ -70,12 +66,21 @@ export function TerminalWindowChrome({
|
|||||||
{sessionTitle}
|
{sessionTitle}
|
||||||
</span>
|
</span>
|
||||||
{connected != null && (
|
{connected != null && (
|
||||||
|
<span
|
||||||
|
role="status"
|
||||||
|
title={connected ? connectedLabel : disconnectedLabel}
|
||||||
|
className="flex shrink-0 items-center gap-1"
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-1.5 w-1.5 shrink-0 rounded-full',
|
'h-1.5 w-1.5 rounded-full',
|
||||||
connected ? 'bg-[#28c840]' : 'bg-[#ff5f57]',
|
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>}
|
{trailing && <div className="ml-auto flex min-w-0 items-center gap-1">{trailing}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -128,7 +128,11 @@ export function ServerManager() {
|
|||||||
const getServerDisplayName = (server: JsonData) =>
|
const getServerDisplayName = (server: JsonData) =>
|
||||||
server.alias || server.cloud_instance_name || server.hostname || server.ip || "";
|
server.alias || server.cloud_instance_name || server.hostname || server.ip || "";
|
||||||
|
|
||||||
|
// 详情抽屉内打开终端/RDP/文件时先关闭抽屉,避免同 z-index 的 Sheet 遮挡弹窗 // Logan.Li
|
||||||
|
const closeDetailSheet = () => setDetailServer(null);
|
||||||
|
|
||||||
const openTerminal = (server: JsonData) => {
|
const openTerminal = (server: JsonData) => {
|
||||||
|
closeDetailSheet();
|
||||||
setTerminalServer({
|
setTerminalServer({
|
||||||
id: server.id,
|
id: server.id,
|
||||||
name: getServerDisplayName(server),
|
name: getServerDisplayName(server),
|
||||||
@@ -140,11 +144,13 @@ export function ServerManager() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openRdp = (server: JsonData) => {
|
const openRdp = (server: JsonData) => {
|
||||||
|
closeDetailSheet();
|
||||||
setRdpServer({ id: server.id, name: getServerDisplayName(server), ip: server.ip });
|
setRdpServer({ id: server.id, name: getServerDisplayName(server), ip: server.ip });
|
||||||
setRdpOpen(true);
|
setRdpOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openFileBrowser = (server: JsonData) => {
|
const openFileBrowser = (server: JsonData) => {
|
||||||
|
closeDetailSheet();
|
||||||
setFileBrowserServer({ id: server.id, name: getServerDisplayName(server) });
|
setFileBrowserServer({ id: server.id, name: getServerDisplayName(server) });
|
||||||
setFileBrowserOpen(true);
|
setFileBrowserOpen(true);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ interface TerminalDialogProps {
|
|||||||
serverName: string;
|
serverName: string;
|
||||||
serverIp: string;
|
serverIp: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
/** ????????????????? SSH ??? // Logan.Li */
|
/** 资产是否已配置 SSH 凭据,用于终端连接前的判断 // Logan.Li */
|
||||||
hasAuth?: boolean;
|
hasAuth?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,8 +227,10 @@ export function TerminalDialog({
|
|||||||
onToggleFullscreen={() => setFullscreen(!fullscreen)}
|
onToggleFullscreen={() => setFullscreen(!fullscreen)}
|
||||||
onClose={() => onOpenChange(false)}
|
onClose={() => onOpenChange(false)}
|
||||||
closeLabel={t("common.close")}
|
closeLabel={t("common.close")}
|
||||||
fullscreenLabel={t("server.detailSheet.fullscreen")}
|
fullscreenLabel={t("common.fullscreen")}
|
||||||
restoreLabel={t("server.detailSheet.exitFullscreen")}
|
restoreLabel={t("common.exitFullscreen")}
|
||||||
|
connectedLabel={t("common.connected")}
|
||||||
|
disconnectedLabel={t("common.disconnected")}
|
||||||
/>
|
/>
|
||||||
{!authReady && (
|
{!authReady && (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import {
|
import {
|
||||||
TerminalWindowChrome,
|
TerminalWindowChrome,
|
||||||
@@ -40,6 +41,7 @@ interface LiveMonitorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LiveMonitorDialog({ open, onOpenChange, sessionId, sessionInfo }: LiveMonitorProps) {
|
export function LiveMonitorDialog({ open, onOpenChange, sessionId, sessionInfo }: LiveMonitorProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const termRef = useRef<XTerm | null>(null);
|
const termRef = useRef<XTerm | null>(null);
|
||||||
const fitRef = useRef<FitAddon | null>(null);
|
const fitRef = useRef<FitAddon | null>(null);
|
||||||
@@ -198,17 +200,19 @@ export function LiveMonitorDialog({ open, onOpenChange, sessionId, sessionInfo }
|
|||||||
: { maxWidth: '960px', width: '92vw' }),
|
: { maxWidth: '960px', width: '92vw' }),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle className="sr-only">终端实时监控</DialogTitle>
|
<DialogTitle className="sr-only">{t('system.liveMonitor.title')}</DialogTitle>
|
||||||
<DialogDescription className="sr-only">审计在线 WebSSH 会话</DialogDescription>
|
<DialogDescription className="sr-only">{t('system.liveMonitor.description')}</DialogDescription>
|
||||||
<TerminalWindowChrome
|
<TerminalWindowChrome
|
||||||
sessionTitle={`${meta?.username || 'monitor'}@${meta?.server_name || 'host'}:~`}
|
sessionTitle={`${meta?.username || 'monitor'}@${meta?.server_name || 'host'}:~`}
|
||||||
connected={connected}
|
connected={connected}
|
||||||
fullscreen={fullscreen}
|
fullscreen={fullscreen}
|
||||||
onToggleFullscreen={() => setFullscreen(!fullscreen)}
|
onToggleFullscreen={() => setFullscreen(!fullscreen)}
|
||||||
onClose={() => onOpenChange(false)}
|
onClose={() => onOpenChange(false)}
|
||||||
closeLabel="关闭"
|
closeLabel={t('common.close')}
|
||||||
fullscreenLabel="全屏"
|
fullscreenLabel={t('common.fullscreen')}
|
||||||
restoreLabel="退出全屏"
|
restoreLabel={t('common.exitFullscreen')}
|
||||||
|
connectedLabel={t('common.connected')}
|
||||||
|
disconnectedLabel={t('common.disconnected')}
|
||||||
trailing={
|
trailing={
|
||||||
<>
|
<>
|
||||||
{meta && (
|
{meta && (
|
||||||
@@ -218,18 +222,18 @@ export function LiveMonitorDialog({ open, onOpenChange, sessionId, sessionInfo }
|
|||||||
)}
|
)}
|
||||||
{connected && (
|
{connected && (
|
||||||
<Badge variant="outline" className="border-emerald-500/30 bg-emerald-500/10 text-xs text-emerald-400">
|
<Badge variant="outline" className="border-emerald-500/30 bg-emerald-500/10 text-xs text-emerald-400">
|
||||||
在线
|
{t('common.online')}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{!connected && !connectError && (
|
{!connected && !connectError && (
|
||||||
<Badge variant="outline" className="border-yellow-500/30 bg-yellow-500/10 text-xs text-yellow-400">
|
<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" />
|
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
|
||||||
连接中
|
{t('system.liveMonitor.connecting')}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{connectError && (
|
{connectError && (
|
||||||
<Badge variant="outline" className="border-red-500/30 bg-red-500/10 text-xs text-red-400">
|
<Badge variant="outline" className="border-red-500/30 bg-red-500/10 text-xs text-red-400">
|
||||||
连接失败
|
{t('system.liveMonitor.connectFailed')}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in new issue
Block a user