From 1a693f70ac8b35b5860136e1a2cbfe97d7a15aba Mon Sep 17 00:00:00 2001 From: jialin Date: Tue, 23 Sep 2025 11:50:56 +0800 Subject: [PATCH] feat: worker terminal --- src/components/x-terminal/index.tsx | 2 +- .../_components/terminal-tabs/footer.less | 40 +++++++++++++++ src/pages/_components/terminal-tabs/index.tsx | 30 ++++++++++++ src/pages/_components/terminal-tabs/tabs.tsx | 49 +++++++++++++++++++ src/pages/_components/terminal-tabs/types.ts | 4 ++ src/pages/resources/components/workers.tsx | 41 +++++++++++++++- 6 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 src/pages/_components/terminal-tabs/footer.less create mode 100644 src/pages/_components/terminal-tabs/index.tsx create mode 100644 src/pages/_components/terminal-tabs/tabs.tsx create mode 100644 src/pages/_components/terminal-tabs/types.ts diff --git a/src/components/x-terminal/index.tsx b/src/components/x-terminal/index.tsx index e466a1c6..2b7791f1 100644 --- a/src/components/x-terminal/index.tsx +++ b/src/components/x-terminal/index.tsx @@ -1,11 +1,11 @@ import useUserSettings from '@/hooks/use-user-settings'; import { FitAddon } from '@xterm/addon-fit'; import { Terminal } from '@xterm/xterm'; +import '@xterm/xterm/css/xterm.css'; import { createStyles } from 'antd-style'; import _ from 'lodash'; import qs from 'query-string'; import React, { forwardRef, useEffect, useImperativeHandle } from 'react'; -import 'xterm/css/xterm.css'; const useStyles = createStyles(({ token, css }) => ({ wrap: css` diff --git a/src/pages/_components/terminal-tabs/footer.less b/src/pages/_components/terminal-tabs/footer.less new file mode 100644 index 00000000..05dea633 --- /dev/null +++ b/src/pages/_components/terminal-tabs/footer.less @@ -0,0 +1,40 @@ +.ant-pro-footer-bar { + padding-inline: 0; + border-block-start: 1px solid rgba(5, 5, 5, 6%); + background-color: #f5f5f5; + border-radius: 4px 4px 0 0; + + .ant-pro-footer-bar-left { + display: none; + } + + .ant-pro-footer-bar-right { + flex: 1; + } + + .ant-tabs-nav { + .ant-tabs-tab { + border: none; + background-color: transparent; + + .ant-tabs-tab-remove { + display: none; + } + + &:hover { + .ant-tabs-tab-remove { + display: inline-block; + } + } + } + + .ant-tabs-tab:not(.ant-tabs-tab-active) { + color: var(--ant-color-text-secondary); + } + + .ant-tabs-tab-active { + background-color: var(--ant-color-bg-elevated); + color: var(--ant-color-text); + } + } +} diff --git a/src/pages/_components/terminal-tabs/index.tsx b/src/pages/_components/terminal-tabs/index.tsx new file mode 100644 index 00000000..4a3e81e0 --- /dev/null +++ b/src/pages/_components/terminal-tabs/index.tsx @@ -0,0 +1,30 @@ +import './footer.less'; +import TerminalTabs from './tabs'; + +export interface TerminalModalProps { + terminals: { url: string; name: string }[]; + height: number; + open: boolean; + onClose: () => void; + currentActive?: string; +} + +const TerminalModal: React.FC = ({ + terminals, + height, + open, + onClose, + currentActive +}) => { + return ( +
+ +
+ ); +}; + +export default TerminalModal; diff --git a/src/pages/_components/terminal-tabs/tabs.tsx b/src/pages/_components/terminal-tabs/tabs.tsx new file mode 100644 index 00000000..3fec1267 --- /dev/null +++ b/src/pages/_components/terminal-tabs/tabs.tsx @@ -0,0 +1,49 @@ +import XTerminal from '@/components/x-terminal'; +import { Tabs } from 'antd'; +import React, { useEffect, useMemo, useState } from 'react'; +import { TerminalProps } from './types'; + +export interface TerminalTabsProps { + terminals: TerminalProps[]; + height: number; + currentActive?: string; +} + +const TerminalTabs: React.FC = ({ + terminals, + height, + currentActive +}) => { + const [activeKey, setActiveKey] = useState( + currentActive || terminals[0]?.url || '' + ); + + const items = useMemo(() => { + return terminals.map((terminal) => { + return { + key: terminal.url, + label: terminal.name, + children: + }; + }); + }, [terminals]); + + useEffect(() => { + if (currentActive) { + setActiveKey(currentActive); + } + }, [currentActive]); + + return ( + + ); +}; + +export default TerminalTabs; diff --git a/src/pages/_components/terminal-tabs/types.ts b/src/pages/_components/terminal-tabs/types.ts new file mode 100644 index 00000000..340c6eb4 --- /dev/null +++ b/src/pages/_components/terminal-tabs/types.ts @@ -0,0 +1,4 @@ +export interface TerminalProps { + url: string; + name: string; +} diff --git a/src/pages/resources/components/workers.tsx b/src/pages/resources/components/workers.tsx index 3a31fdc4..b23dd0b4 100644 --- a/src/pages/resources/components/workers.tsx +++ b/src/pages/resources/components/workers.tsx @@ -7,6 +7,7 @@ import { useIntl } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { ConfigProvider, Empty, Table, message } from 'antd'; import React, { useEffect, useState } from 'react'; +import TerminalTabs from '../../_components/terminal-tabs'; import { WORKERS_API, deleteWorker, @@ -64,6 +65,16 @@ const Workers: React.FC = () => { open: false, currentData: null }); + const [terminals, setTerminals] = useState<{ url: string; name: string }[]>( + [] + ); + const [terminalModalStatus, setTerminalModalStatus] = useState<{ + open: boolean; + currentActive: string; + }>({ + open: false, + currentActive: '' + }); const getClusterList = async () => { try { @@ -97,7 +108,6 @@ const Workers: React.FC = () => { const handleUpdateLabelsOk = async (values: Record) => { try { - console.log('updateLabelsData.data', updateLabelsData.data); await updateWorker(updateLabelsData.data.id, { ...updateLabelsData.data, labels: values.labels @@ -118,7 +128,6 @@ const Workers: React.FC = () => { }; const handleUpdateLabels = (record: ListItem) => { - console.log('record', record); setUpdateLabelsData({ open: true, data: { @@ -151,6 +160,19 @@ const Workers: React.FC = () => { name: record.name }); } + if (val === 'terminal') { + const url = `/api/workers/${record.id}/terminal`; + setTerminals((prev) => { + return [ + ...prev, + { + url, + name: record.name + } + ]; + }); + setTerminalModalStatus({ open: true, currentActive: url }); + } }); const renderEmpty = (type?: string) => { @@ -172,6 +194,11 @@ const Workers: React.FC = () => { }); }; + const handleTerminalClose = () => { + setTerminalModalStatus({ open: false, currentActive: '' }); + setTerminals([]); + }; + const columns = useWorkerColumns({ clusterData, loadend: dataSource.loadend, @@ -187,6 +214,16 @@ const Workers: React.FC = () => { <> + ]} header={{ title: 'Workers', style: {