diff --git a/packages/material/src/centerLink/CenterLink.tsx b/packages/material/src/centerLink/CenterLink.tsx new file mode 100644 index 0000000..2adb726 --- /dev/null +++ b/packages/material/src/centerLink/CenterLink.tsx @@ -0,0 +1,65 @@ +import React,{useRef} from 'react' +import TerminalForm from './components/TerminalForm' +import WebTerminal from './components/WebTerminal' +import './index.less'; +import style from 'packages/meta/src/badge/style'; + +interface CenterLinkProps{ + websocketUrl:string; // websocket服务地址 + token:string; // 用户token信息 + ip:string; // IP地址 + onExportLogs:()=>void, // 导出日志事件 + terminalStyle?:React.CSSProperties; + onConnect?:(values:any) => void, // 连接服务器事件 + onOpen?: (event: WebSocketEventMap['open'], instance: WebSocket) => void; + onClose?: (event: WebSocketEventMap['close'], instance: WebSocket) => void; + onMessage?: (message: WebSocketEventMap['message'], instance: WebSocket) => void; + onError?: (event: WebSocketEventMap['error'], instance: WebSocket) => void; + style?:React.CSSProperties; +} + +const materialName='center-link'; + +const CenterLink:React.FC=(props:CenterLinkProps)=>{ + const { + websocketUrl, + ip, + token, + terminalStyle, + onConnect, + onExportLogs, + onOpen, + onClose, + onMessage, + onError, + style + }=props; + const webRef=useRef(null); + + // 处理开始连接服务器ip的事件 + const handleConnectClick=(values:any)=>{ + const {ip}=values; + if(ip&&token&&websocketUrl&&webRef.current){ + webRef.current.connect(); + } + onConnect&&onConnect(values); + } + return ( +
+ + +
+ ) +} + +export default CenterLink \ No newline at end of file diff --git a/packages/material/src/centerLink/components/TerminalForm.tsx b/packages/material/src/centerLink/components/TerminalForm.tsx new file mode 100644 index 0000000..9472a9b --- /dev/null +++ b/packages/material/src/centerLink/components/TerminalForm.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { Button, Form, Input,FormProps } from '@zhst/meta'; +import '../index.less'; + +const materialName='terminal-form'; + +export interface TerminalFormProps{ + onConnect:FormProps['onFinish']; // 开始连接事件 + onExportLogs:()=>void; // 导出日志事件 +} + +const TerminalForm:React.FC=(props:TerminalFormProps)=> { + const { + onConnect, + onExportLogs, + }=props; + const [form] = Form.useForm(); + return ( +
+

连接中心服务器

+
+ + + + + + + + + +
+
+ ) +} + +export default TerminalForm; \ No newline at end of file diff --git a/packages/material/src/centerLink/components/WebTerminal.tsx b/packages/material/src/centerLink/components/WebTerminal.tsx new file mode 100644 index 0000000..4beafaf --- /dev/null +++ b/packages/material/src/centerLink/components/WebTerminal.tsx @@ -0,0 +1,133 @@ + +import React,{useRef,useEffect, useImperativeHandle, + forwardRef,} from 'react'; +import { useWebSocket } from '@zhst/hooks'; +import { Terminal } from 'xterm'; +import { FitAddon } from 'xterm-addon-fit'; +import 'xterm/css/xterm.css'; +import '../index.less'; + + +export interface WebsocketOptions { + reconnectLimit?: number; // 重试次数 + reconnectInterval?: number; // 重试时间间隔(ms) + onOpen?: (event: WebSocketEventMap['open'], instance: WebSocket) => void; // webSocket 连接成功回调 + onClose?: (event: WebSocketEventMap['close'], instance: WebSocket) => void; // webSocket 关闭回调 + onMessage?: (message: WebSocketEventMap['message'], instance: WebSocket) => void; // webSocket 收到消息回调 + onError?: (event: WebSocketEventMap['error'], instance: WebSocket) => void; // webSocket 错误回调 + protocols?: string | string[]; // 子协议 +} + +enum ReadyState { + Connecting = 0, + Open = 1, + Closing = 2, + Closed = 3, +} + +export interface WebsocketResult { + latestMessage?: WebSocketEventMap['message']; // 最新消息 + sendMessage: WebSocket['send']; // 发送消息函数 + disconnect: () => void; // 手动断开 webSocket 连接 + connect: () => void; // 手动连接 webSocket,如果当前已有连接,则关闭后重新连接 + readyState: ReadyState; // 当前 webSocket 连接状态 + webSocketIns?: WebSocket; // webSocket 实例 +} +interface WebTerminalProps{ + websocketUrl:string; // websocket服务地址 + token:string; // 用户token信息 + ip:string; // IP地址 + terminalStyle?:React.CSSProperties; + onOpen?: (event: WebSocketEventMap['open'], instance: WebSocket) => void; + onClose?: (event: WebSocketEventMap['close'], instance: WebSocket) => void; + onMessage?: (message: WebSocketEventMap['message'], instance: WebSocket) => void; + onError?: (event: WebSocketEventMap['error'], instance: WebSocket) => void; +} +const materialName = 'web-terminal' + +const WebTerminal:React.FC=forwardRef((props:WebTerminalProps&WebsocketOptions,ref)=> { + const { + websocketUrl='', + token='', + ip='', + terminalStyle, + onOpen, + onClose, + onMessage, + onError, + }=props; + const { readyState, sendMessage, latestMessage, disconnect, connect }:WebsocketResult = useWebSocket( + `${websocketUrl}?ip=${ip}&Authorization=${token}`,{manual:true,reconnectLimit:0,onOpen, + onClose, + onMessage, + onError} + ); + const termRef = useRef(null); + const termClassRef=useRef(null) + // const currLine=useRef(null); + + useEffect(()=>{ + if(termClassRef?.current){ + termClassRef.current.write(latestMessage?.data+'\r\n\x1b[33m$\x1b[0m '); + } + },[latestMessage]); + + // terminal初始化 + useEffect(()=>{ + // 初始化terminal + if(!termRef.current){ + return; + } + termClassRef.current=new Terminal({ + fontFamily: 'Menlo, Monaco, "Courier New", monospace', + fontWeight: 400, + fontSize: 14, + rows: Math.ceil( + (termRef.current?.clientHeight - + 150) / + 14, + ), + convertEol: true,//控制终端是否自动将 \n 转换为 \r\n。 + cursorBlink: true,//指定光标是否闪烁 + scrollback: 50, //终端中的回滚量 + disableStdin: false, //是否应禁用输入。 + cursorStyle: "underline", //光标样式 + windowsMode: true, // 根据窗口换行 + theme: { + foreground: "#ffffff", //字体 + background: "#1a1a1d", //背景色 + cursor: "help", //设置光标 + } + }) + + let term=termClassRef.current; + term.open(termRef.current); + term.focus(); // 光标聚集 + term.promp=(_)=>{ + term.write('\r\n\x1b[33m$\x1b[0m '); + } + const fitAddon=new FitAddon(); + term.loadAddon(fitAddon); + fitAddon.fit(); + term.promp(); + + },[]); + + // 自定义暴露给父组件的实例 + useImperativeHandle(ref,()=>({ + readyState, + sendMessage, + latestMessage, + disconnect, + connect + })); + + return ( +
+
+
+ + ) +}) + +export default WebTerminal; \ No newline at end of file diff --git a/packages/material/src/centerLink/demo/basic.tsx b/packages/material/src/centerLink/demo/basic.tsx new file mode 100644 index 0000000..2f33cda --- /dev/null +++ b/packages/material/src/centerLink/demo/basic.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { CenterLink } from '@zhst/material'; + +const demo = () => { + return ( + { + console.log(values,'====> Connecting'); + }} + onExportLogs={()=>{console.log('=====> Export Log')}} + onOpen={(event: WebSocketEventMap['open'], instance: WebSocket)=>{ + console.log(event,'===>open'); // webSocket 连接成功回调 + }} + onClose={(event: WebSocketEventMap['close'], instance: WebSocket)=>{ + console.log(event,'===>close'); // webSocket 关闭回调 + }} + onMessage={(message: WebSocketEventMap['message'], instance: WebSocket)=>{ + console.log(message,'===>message'); // webSocket 消息回调 + }} + onError={(event: WebSocketEventMap['error'], instance: WebSocket)=>{ + console.log(event,'===>error'); // webSocket 错误回调 + }} + /> + ); +}; + +export default demo; diff --git a/packages/material/src/centerLink/index.less b/packages/material/src/centerLink/index.less new file mode 100644 index 0000000..02b8d3e --- /dev/null +++ b/packages/material/src/centerLink/index.less @@ -0,0 +1,32 @@ +.center-link{ + width: 100%; + height: 100%; + padding: 30px; + box-sizing: border-box; + background-color: #E5EAEC; + overflow: hidden; +} + +.web-terminal{ + width: 100%; + height: 100%; + // padding: 30px; + // box-sizing: border-box; + // background-color: #E5EAEC; + // overflow: hidden; +} + +.terminal-form{ + width: 100%; + margin-bottom: 30px; + + h1{ + font-family: SourceHanSansCN, SourceHanSansCN; + font-weight: bold; + font-size:16px; + text-align: left; + line-height: 24px; + margin-bottom: 20px; + color: rgba(0,0,0,80%); + } +} \ No newline at end of file diff --git a/packages/material/src/centerLink/index.md b/packages/material/src/centerLink/index.md new file mode 100644 index 0000000..c768fcc --- /dev/null +++ b/packages/material/src/centerLink/index.md @@ -0,0 +1,30 @@ +--- +category: Components +title: CenterLink 中心对接 +toc: content +group: + title: 通用 + order: 2 +--- + +中心对接 + +## 代码演示 + +基本用法 + +## API + +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| websocketUrl | websocket地址【必传】 | string | - | - | +| ip | 服务器ip地址【必传】 | string | - | - | +| token | 用户token信息【必传】 | string | - | - | +| terminalStyle | 终端黑盒子样式【可选传】 | React.CSSProperties | - | - | +| style | 整个页面的样式【可选传】 | React.CSSProperties | - | - | +| onExportLogs | 导出日志事件【可选传】 | ()=>void | - | - | +| onConnect | 连接服务器事件【可选传】 | (values:any) => void | - | - | +| onOpen | webSocket 连接成功回调【可选传】 | (event: WebSocketEventMap['open'], instance: WebSocket) => void | - | - | +| onClose | webSocket 关闭回调【可选传】 | (event: WebSocketEventMap['close'], instance: WebSocket) => void | - | - | +| onMessage | webSocket 收到消息回调【可选传】 | (event:WebSocketEventMap['message'], instance: WebSocket) => void | - | - | +| onError | webSocket 错误回调【可选传】 | (event: WebSocketEventMap['error'], instance: WebSocket) => void | - | - | \ No newline at end of file diff --git a/packages/material/src/centerLink/index.tsx b/packages/material/src/centerLink/index.tsx new file mode 100644 index 0000000..2b3666e --- /dev/null +++ b/packages/material/src/centerLink/index.tsx @@ -0,0 +1,3 @@ +import CenterLink from "./CenterLink"; + +export default CenterLink; \ No newline at end of file diff --git a/packages/material/src/index.tsx b/packages/material/src/index.tsx index e7a57e6..7971d2e 100644 --- a/packages/material/src/index.tsx +++ b/packages/material/src/index.tsx @@ -3,6 +3,6 @@ export { default as AlgorithmConfig } from './algorithmConfig'; export type { AlgorithmConfigRef, AlgorithmConfigProps } from './algorithmConfig'; export { default as Login } from './login'; export { default as Password } from './password'; -export { default as Terminal } from './terminal'; +export { default as CenterLink } from './centerLink'; export { default as SchemaFormModal } from './algorithmConfig/components/schemaFormModal'; export * from 'rc-util' diff --git a/packages/material/src/terminal/Terminal.tsx b/packages/material/src/terminal/Terminal.tsx deleted file mode 100644 index 26ad084..0000000 --- a/packages/material/src/terminal/Terminal.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { Button, Form, Input,FormProps } from '@zhst/meta'; -import React from 'react'; -import WebTerminal from './components/WebTerminal'; -import './index.less'; - -interface TerminalProps{ - onFinish:FormProps['onFinish']; - onExportLog:(filePath:string)=>void; - websocketUrl:string; // websocket地址 - token:string; // 用户token信息 - ip:string; // ip地址 - // filePath:string; // 导出日志的文件地址 -} -const materialName = 'zhst-material-terminal' -const Terminal: React.FC = (props:TerminalProps) => { - const {onFinish,websocketUrl='',token='',ip='',onExportLog}=props; - const [form] = Form.useForm(); - - const handleCenterConnect = async () => { - const values = await form.validateFields(); - onFinish&&onFinish(values); - }; - - return ( -
-

连接中心服务器

-
- - - - - - - - - -
- -
- ); -}; - -export default Terminal; diff --git a/packages/material/src/terminal/components/WebTerminal.tsx b/packages/material/src/terminal/components/WebTerminal.tsx deleted file mode 100644 index 911164e..0000000 --- a/packages/material/src/terminal/components/WebTerminal.tsx +++ /dev/null @@ -1,164 +0,0 @@ -// @ts-nocheck -import React, {Component,useRef,useEffect } from 'react'; -import { Terminal } from 'xterm'; -import { FitAddon } from 'xterm-addon-fit'; -import WebsocketTerm from './WebsocketTerm'; -import 'xterm/css/xterm.css'; - -// TODO:引入xterm 后续需要和后端在建立websocket连接再次调试 -export default class WebTerminal extends Component { - term = null; - websocket = null; - curr_line = ''; - websocketUrl=''; // websocket服务地址 - token=''; // 用户token信息 - ip=''; // IP地址 - constructor(props:any){ - super(props); - this.websocketUrl = props.websocketUrl; - this.token=props.token; - this.ip=props.ip; - } - componentDidMount() { - let term = this.term; - // term初始化 - this.term = new Terminal({ - fontFamily: 'Menlo, Monaco, "Courier New", monospace', - fontWeight: 400, - fontSize: 14, - rows: Math.ceil( - (document.getElementsByClassName('container-children')[0].clientHeight - - 150) / - 14, - ), - convertEol: true,//控制终端是否自动将 \n 转换为 \r\n。 - cursorBlink: true,//指定光标是否闪烁 - scrollback: 50, //终端中的回滚量 - disableStdin: false, //是否应禁用输入。 - cursorStyle: "underline", //光标样式 - windowsMode: true, // 根据窗口换行 - theme: { - foreground: "#ffffff", //字体 - background: "#1a1a1d", //背景色 - cursor: "help", //设置光标 - } - }); - this.term.open(document.getElementById('terminal')); - this.term.focus(); // 光标聚集 - this.term.prompt = (_) => { - this.term.write('\r\n\x1b[33m$\x1b[0m '); - }; - // // 换行并输入起始符 - // this.term.prompt = (_) => { - // this.term.write("\r\n>>> ") - // } - - if(this.ip!==''){ - this.term.write('root@'+this.ip); - } - const fitAddon = new FitAddon(); - this.term.loadAddon(fitAddon); - fitAddon.fit(); - this.term.prompt(); - - // 添加事件监听器,支持输入方法 - this.term.onKey((e) => { - const printable = - !e.domEvent.altKey && - !e.domEvent.altGraphKey && - !e.domEvent.ctrlKey && - !e.domEvent.metaKey; - if (e.domEvent.keyCode === 13) { - this.Send(term, this.curr_line); - this.term.prompt(); - this.curr_line = ''; - } else if (e.domEvent.keyCode === 8) { - // back 删除的情况 - if (this.term._core.buffer.x > 2) { - if (this.curr_line.length) { - this.curr_line = this.curr_line.slice(0, this.curr_line.length - 1); - this.term.write('\b \b'); - } else { - } - } - } else if (printable) { - this.curr_line += e.key; - this.term.write(e.key); - } - this.term.focus(); - }); - this.term.onData((key) => { - // 粘贴的情况 - if (key.length > 1) { - this.term.write(key); - this.curr_line += key; - } - }); - this.initWebsock(); - if(this.websocket){ - // 只读属性 readyState 表示连接状态,可以是以下值 - // 0 - 表示连接尚未建立。 - // 1 - 表示连接已建立,可以进行通信。 - // 2 - 表示连接正在进行关闭。 - // 3 - 表示连接已经关闭或者连接不能打开 - - // websocket建立连接时发送ip以及token给后端 - if(this.websocket.readyState ===1){ - this.Send(this.term,{ip:this.ip,Authorization:this.token}) - } - } - } - componentWillUnmount() { - this.term.dispose(); - // WebSocket 方法 关闭连接 - this.websocket.close(); - } - - - initWebsock = () => { - let term = this.term; - let token = this.token; - let ip = this.ip; - // let preSuffix=location.protocol === 'http:' ? 'ws://' : 'wss://'; - let preSuffix='ws://127.0.0.1:50051/active'; - // 初始化 - this.websocket = new WebSocket('ws://127.0.0.1:50051/active'); - // WebSocket 事件 - // 连接建立时触发 - this.websocket.onopen = function (evt) { - term.write('connect'); - }; - - // 连接关闭时触发 - this.websocket.onclose = function (evt) { - term.write('exit'); - }; - - // 客户端接收服务端数据时触发 - this.websocket.onmessage = function (evt) { - term.write(evt.data); - }; - - // 通信发生错误时触发 - this.websocket.onerror = function (evt) { - term.write('connect fail err:' + evt.data); - }; - }; - - prompt = (term) => { - this.term.write('\r\n~$ '); - }; - - // WebSocket 方法 使用连接发送数据 - Send = (term, message) => { - this.websocket.send(message); - }; - - render() { - return ( -
-
-
- ); - } -} diff --git a/packages/material/src/terminal/demo/basic.tsx b/packages/material/src/terminal/demo/basic.tsx deleted file mode 100644 index 0986155..0000000 --- a/packages/material/src/terminal/demo/basic.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { Terminal } from '@zhst/material'; - -const demo = () => { - - return ( - console.log('val', val)} - websocketUrl={'ws://127.0.0.1:30003'} - token={'this is user token'} - ip={'127.0.0.1'} - onExportLog={(url)=>{console.log(url,'====>url');}} - /> - ); -}; - -export default demo; diff --git a/packages/material/src/terminal/index.less b/packages/material/src/terminal/index.less deleted file mode 100644 index 69998b1..0000000 --- a/packages/material/src/terminal/index.less +++ /dev/null @@ -1,64 +0,0 @@ -.zhst-material-terminal{ - width: 100%; - height: 100vh; - background-color: #E5EAEC; - box-sizing: border-box; - padding:30px; - overflow: hidden; - - h1{ - font-family: SourceHanSansCN, SourceHanSansCN; - font-weight: bold; - font-size:16px; - text-align: left; - line-height: 24px; - margin-bottom: 20px; - color: rgba(0,0,0,80%); - } - - -} - -.container-children{ - margin-top: 30px; - width: 100%; - height: calc(100vh - 180px); - box-sizing: border-box; - - #terminal{ - width: 100%; - height: calc(100vh - 180px); - - .xterm-screen{ - width: 100%; - height: 100%; - background-color: #000; - } - - } - - - -} - -.zhst-form-item .zhst-form-item-control-input-content{ - .zhst-btn-default:not(:disabled):not(.zhst-btn-disabled):hover{ - color: #23ACB2; - border-color:#23acb2; - } - - .zhst-input-affix-wrapper:focus{ - border-color:#23acb2; - } - - .zhst-input-affix-wrapper:hover{ - border-color:#23acb2; - } -} - - - -*{ - margin: 0; - padding: 0; -} \ No newline at end of file diff --git a/packages/material/src/terminal/index.md b/packages/material/src/terminal/index.md deleted file mode 100644 index 1e2c5ea..0000000 --- a/packages/material/src/terminal/index.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -category: Components -title: Terminal 中心对接 -toc: content -group: - title: 通用 - order: 2 ---- - -中心对接 - -## 代码演示 - -基本用法 - -## API - -| 参数 | 说明 | 类型 | 默认值 | 版本 | -| --- | --- | --- | --- | --- | -| onFinish | 提交事件 | FormProps['onFinish'] | - | - | diff --git a/packages/material/src/terminal/index.tsx b/packages/material/src/terminal/index.tsx deleted file mode 100644 index 6f61da8..0000000 --- a/packages/material/src/terminal/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import Terminal from './Terminal' - -export default Terminal; \ No newline at end of file diff --git a/packages/material/src/utils/index.ts b/packages/material/src/utils/index.ts index e69de29..999a0eb 100644 --- a/packages/material/src/utils/index.ts +++ b/packages/material/src/utils/index.ts @@ -0,0 +1,15 @@ +// 可应用于页面跳转以及文件下载 +// 第一个参数:文件的下载路径/要跳转页面的路径(可携带参数) +// 第二个参数:是否新打开一个页面,true为新开一个页面,false是在当前页面进行操作; +export const createAElement = (url: string, isBlank: boolean) => { + var newLink = document.createElement('a'); + newLink.className = 'create-link'; + newLink.href = url; + if (isBlank) { + newLink.target = '_blank'; + } + document.body.appendChild(newLink); + newLink.click(); + document.body.removeChild(newLink); + }; + \ No newline at end of file