修改SSH链接属性字段

This commit is contained in:
dengqichen 2025-12-06 17:01:42 +08:00
parent 00628d2811
commit 5b6a06016e
5 changed files with 74 additions and 48 deletions

View File

@ -40,13 +40,13 @@ interface SSHTerminalContentProps {
onStatusChange?: (status: ConnectionStatus) => void; // 状态变化回调 onStatusChange?: (status: ConnectionStatus) => void; // 状态变化回调
} }
type ConnectionStatus = 'initializing' | 'connecting' | 'connected' | 'disconnecting' | 'disconnected' | 'error'; type ConnectionStatus = 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'error';
interface WebSocketMessage { interface WebSocketMessage {
type: 'output' | 'error' | 'status'; type: 'output' | 'input' | 'status' | 'error';
data?: string; data: string;
message?: string; timestamp?: number;
status?: ConnectionStatus; metadata?: Record<string, any> | null;
} }
export const SSHTerminalContent: React.FC<SSHTerminalContentProps> = ({ export const SSHTerminalContent: React.FC<SSHTerminalContentProps> = ({
@ -65,7 +65,7 @@ export const SSHTerminalContent: React.FC<SSHTerminalContentProps> = ({
const onStatusChangeRef = useRef(onStatusChange); const onStatusChangeRef = useRef(onStatusChange);
const isClosingRef = useRef<boolean>(false); const isClosingRef = useRef<boolean>(false);
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('initializing'); const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('connecting');
const [errorMessage, setErrorMessage] = useState<string>(''); const [errorMessage, setErrorMessage] = useState<string>('');
const [fontSize, setFontSize] = useState<number>(14); const [fontSize, setFontSize] = useState<number>(14);
const [showSearch, setShowSearch] = useState<boolean>(false); const [showSearch, setShowSearch] = useState<boolean>(false);
@ -158,7 +158,7 @@ export const SSHTerminalContent: React.FC<SSHTerminalContentProps> = ({
} }
} }
setConnectionStatus('initializing'); setConnectionStatus('connecting');
setErrorMessage(''); setErrorMessage('');
const terminal = new Terminal({ const terminal = new Terminal({
@ -275,28 +275,27 @@ export const SSHTerminalContent: React.FC<SSHTerminalContentProps> = ({
break; break;
case 'error': case 'error':
console.error('❌ SSH错误:', msg.message); console.error('❌ SSH错误:', msg.data);
terminalInstanceRef.current?.writeln(`\r\n\x1b[31m错误: ${msg.message}\x1b[0m\r\n`); terminalInstanceRef.current?.writeln(`\r\n\x1b[31m错误: ${msg.data}\x1b[0m\r\n`);
message.error(msg.message || '连接错误'); message.error(msg.data || '连接错误');
break; break;
case 'status': case 'status':
console.log('📊 状态变化:', msg.status); console.log('📊 状态变化:', msg.data);
if (msg.status) { setConnectionStatus(msg.data as ConnectionStatus);
setConnectionStatus(msg.status); if (msg.data === 'connected') {
if (msg.status === 'connected') {
// 显示审计警告 // 显示审计警告
terminalInstanceRef.current?.writeln('\r\n\x1b[33m┌────────────────────────────────────────────────────────────┐\x1b[0m'); terminalInstanceRef.current?.writeln('\r\n\x1b[33m┌─────────────────────────────────────────────────────────────\x1b[0m');
terminalInstanceRef.current?.writeln('\x1b[33m│ ⚠️ 安全提示本次SSH会话将被全程审计记录 │\x1b[0m'); terminalInstanceRef.current?.writeln('\x1b[33m│ ⚠️ 链宇技术有限公司 - 安全提示\x1b[0m');
terminalInstanceRef.current?.writeln('\x1b[33m│ • 所有操作命令、输入、输出都将被完整记录 │\x1b[0m'); terminalInstanceRef.current?.writeln('\x1b[33m│ 本次SSH会话将被全程审计记录\x1b[0m');
terminalInstanceRef.current?.writeln('\x1b[33m│ • 审计日志用于安全审查、故障排查和合规要求 │\x1b[0m'); terminalInstanceRef.current?.writeln('\x1b[33m│ • 所有操作命令、输入、输出都将被完整记录\x1b[0m');
terminalInstanceRef.current?.writeln('\x1b[33m│ • 请规范操作,遵守企业信息安全管理制度 │\x1b[0m'); terminalInstanceRef.current?.writeln('\x1b[33m│ • 审计日志用于安全审查、故障排查和合规要求\x1b[0m');
terminalInstanceRef.current?.writeln('\x1b[33m└────────────────────────────────────────────────────────────┘\x1b[0m\r\n'); terminalInstanceRef.current?.writeln('\x1b[33m│ • 请规范操作,遵守企业信息安全管理制度\x1b[0m');
terminalInstanceRef.current?.writeln('\x1b[33m└─────────────────────────────────────────────────────────────\x1b[0m\r\n');
setTimeout(() => { setTimeout(() => {
fitAddonRef.current?.fit(); fitAddonRef.current?.fit();
}, 100); }, 100);
} }
}
break; break;
} }
} catch (error) { } catch (error) {
@ -376,7 +375,6 @@ export const SSHTerminalContent: React.FC<SSHTerminalContentProps> = ({
const gracefulClose = useCallback(() => { const gracefulClose = useCallback(() => {
console.log('🚪 开始优雅关闭 SSH连接'); console.log('🚪 开始优雅关闭 SSH连接');
isClosingRef.current = true; isClosingRef.current = true;
setConnectionStatus('disconnecting');
terminalInstanceRef.current?.writeln('\r\n\x1b[33m正在断开连接...\x1b[0m'); terminalInstanceRef.current?.writeln('\r\n\x1b[33m正在断开连接...\x1b[0m');
if (wsRef.current) { if (wsRef.current) {
@ -574,14 +572,12 @@ export const SSHTerminalContent: React.FC<SSHTerminalContentProps> = ({
const getStatusBadge = () => { const getStatusBadge = () => {
switch (connectionStatus) { switch (connectionStatus) {
case 'initializing':
return <Badge variant="outline" className="bg-blue-100 text-blue-700"><Loader2 className="mr-1 h-3 w-3 animate-spin" /></Badge>;
case 'connecting': case 'connecting':
return <Badge variant="outline" className="bg-yellow-100 text-yellow-700"><Loader2 className="mr-1 h-3 w-3 animate-spin" /></Badge>; return <Badge variant="outline" className="bg-yellow-100 text-yellow-700"><Loader2 className="mr-1 h-3 w-3 animate-spin" /></Badge>;
case 'connected': case 'connected':
return <Badge variant="outline" className="bg-emerald-100 text-emerald-700"><div className="mr-1 h-2 w-2 rounded-full bg-emerald-500 animate-pulse" /></Badge>; return <Badge variant="outline" className="bg-emerald-100 text-emerald-700"><div className="mr-1 h-2 w-2 rounded-full bg-emerald-500 animate-pulse" /></Badge>;
case 'disconnecting': case 'reconnecting':
return <Badge variant="outline" className="bg-orange-100 text-orange-700"><Loader2 className="mr-1 h-3 w-3 animate-spin" /></Badge>; return <Badge variant="outline" className="bg-orange-100 text-orange-700"><Loader2 className="mr-1 h-3 w-3 animate-spin" /></Badge>;
case 'error': case 'error':
return <Badge variant="outline" className="bg-red-100 text-red-700"><XCircle className="mr-1 h-3 w-3" /></Badge>; return <Badge variant="outline" className="bg-red-100 text-red-700"><XCircle className="mr-1 h-3 w-3" /></Badge>;
case 'disconnected': case 'disconnected':
@ -721,17 +717,17 @@ export const SSHTerminalContent: React.FC<SSHTerminalContentProps> = ({
className="w-full h-full p-2 ssh-terminal-content" className="w-full h-full p-2 ssh-terminal-content"
/> />
{connectionStatus === 'initializing' && ( {connectionStatus === 'connecting' && (
<div className="absolute inset-0 flex flex-col items-center justify-center text-gray-400 bg-[#1e1e1e]"> <div className="absolute inset-0 flex flex-col items-center justify-center text-yellow-400 bg-[#1e1e1e]">
<Loader2 className="h-12 w-12 animate-spin mb-4" /> <Loader2 className="h-12 w-12 animate-spin mb-4" />
<p className="text-lg">SSH终端...</p> <p className="text-lg">SSH...</p>
</div> </div>
)} )}
{connectionStatus === 'disconnecting' && ( {connectionStatus === 'reconnecting' && (
<div className="absolute inset-0 flex flex-col items-center justify-center text-orange-400 bg-[#1e1e1e]"> <div className="absolute inset-0 flex flex-col items-center justify-center text-orange-400 bg-[#1e1e1e]">
<Loader2 className="h-12 w-12 animate-spin mb-4" /> <Loader2 className="h-12 w-12 animate-spin mb-4" />
<p className="text-lg">...</p> <p className="text-lg">...</p>
</div> </div>
)} )}

View File

@ -10,7 +10,7 @@ export interface SSHWindow {
isMinimized: boolean; isMinimized: boolean;
position: { x: number; y: number }; position: { x: number; y: number };
size: { width: number; height: number }; size: { width: number; height: number };
connectionStatus?: 'initializing' | 'connecting' | 'connected' | 'disconnecting' | 'disconnected' | 'error'; connectionStatus?: 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'error';
} }
interface SSHWindowManagerProps { interface SSHWindowManagerProps {
@ -32,7 +32,7 @@ export const SSHWindowManager: React.FC<SSHWindowManagerProps> = ({ onOpenWindow
isMinimized: false, isMinimized: false,
position: { x: 100 + offset, y: 100 + offset }, position: { x: 100 + offset, y: 100 + offset },
size: { width: 1200, height: 700 }, size: { width: 1200, height: 700 },
connectionStatus: 'initializing', connectionStatus: 'connecting',
}; };
setWindows(prev => [...prev, newWindow]); setWindows(prev => [...prev, newWindow]);
@ -123,9 +123,8 @@ export const SSHWindowManager: React.FC<SSHWindowManagerProps> = ({ onOpenWindow
case 'disconnected': case 'disconnected':
return 'bg-red-600 hover:bg-red-700'; return 'bg-red-600 hover:bg-red-700';
case 'connecting': case 'connecting':
case 'initializing':
return 'bg-yellow-600 hover:bg-yellow-700'; return 'bg-yellow-600 hover:bg-yellow-700';
case 'disconnecting': case 'reconnecting':
return 'bg-orange-600 hover:bg-orange-700'; return 'bg-orange-600 hover:bg-orange-700';
default: default:
return 'bg-blue-600 hover:bg-blue-700'; return 'bg-blue-600 hover:bg-blue-700';

View File

@ -0,0 +1,29 @@
import React from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
import type { RootState } from '@/store';
/**
*
*
* 1. ->
* 2. + -> Dashboard
* 3. + ->
*/
const RootRedirect: React.FC = () => {
const token = useSelector((state: RootState) => state.user.token);
const menus = useSelector((state: RootState) => state.user.menus);
const hasMenus = menus && menus.length > 0;
// 未登录,跳转到登录页
if (!token) {
return <Navigate to="/login" replace />;
}
// 已登录,根据菜单权限跳转
return hasMenus
? <Navigate to="/dashboard" replace />
: <Navigate to="/no-permission" replace />;
};
export default RootRedirect;

View File

@ -8,6 +8,7 @@ import store from '../store';
import ProtectedRoute from './ProtectedRoute'; import ProtectedRoute from './ProtectedRoute';
import RouteLoading from '@/components/RouteLoading'; import RouteLoading from '@/components/RouteLoading';
import ErrorFallback from '@/components/ErrorFallback'; import ErrorFallback from '@/components/ErrorFallback';
import RootRedirect from './RootRedirect';
// 错误页面 // 错误页面
const Forbidden = lazy(() => import('@/pages/Error/403')); const Forbidden = lazy(() => import('@/pages/Error/403'));
@ -97,7 +98,7 @@ export const createDynamicRouter = () => {
children: [ children: [
{ {
path: '', path: '',
element: hasMenus ? <Navigate to="/dashboard" replace /> : <Navigate to="/no-permission" replace />, element: <RootRedirect />,
}, },
// 写死的测试路由:表单设计器测试页面 // 写死的测试路由:表单设计器测试页面
{ {
@ -116,9 +117,11 @@ export const createDynamicRouter = () => {
{ {
path: 'no-permission', path: 'no-permission',
element: ( element: (
<ProtectedRoute requiresAuth={true}>
<Suspense fallback={<RouteLoading />}> <Suspense fallback={<RouteLoading />}>
<NoPermission /> <NoPermission />
</Suspense> </Suspense>
</ProtectedRoute>
), ),
}, },
// 403 无权限页面 // 403 无权限页面

View File

@ -2,6 +2,8 @@ import axios, {AxiosRequestConfig, AxiosResponse} from 'axios';
import {toast} from '@/components/ui/use-toast'; import {toast} from '@/components/ui/use-toast';
import { API_BASE_URL, isDev } from '@/config/env'; import { API_BASE_URL, isDev } from '@/config/env';
import { maintenanceDetector } from '@/services/maintenanceDetector'; import { maintenanceDetector } from '@/services/maintenanceDetector';
import store from '../store';
import { logout } from '../store/userSlice';
export interface Response<T = any> { export interface Response<T = any> {
code: number; code: number;
@ -100,11 +102,8 @@ const errorHandler = (error: any) => {
variant: 'destructive' variant: 'destructive'
}); });
// 清除本地存储的所有用户相关信息 // 使用统一的 logout action 清除所有用户数据
localStorage.removeItem('token'); store.dispatch(logout());
localStorage.removeItem('userInfo');
localStorage.removeItem('menus');
localStorage.removeItem('tenantId');
// 延迟跳转,确保提示能显示出来 // 延迟跳转,确保提示能显示出来
setTimeout(() => { setTimeout(() => {