From 009f46879cbfaed952d5308b0774be5522865db2 Mon Sep 17 00:00:00 2001 From: niyyzf Date: Tue, 23 Jul 2024 23:03:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=A7=E4=BF=AE=E8=BF=B7=E4=BD=A0=E7=BB=88?= =?UTF-8?q?=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LoongPanel-Asp/Hubs/TerminalHub.cs | 12 ++- LoongPanel-Asp/Servers/SSHStreamService.cs | 23 +++++- web/components/Term.vue | 93 ++++++++++++++++++---- web/layouts/Main.vue | 11 ++- web/package.json | 1 + web/pages/terminal.vue | 14 +--- web/yarn.lock | 7 ++ 7 files changed, 129 insertions(+), 32 deletions(-) diff --git a/LoongPanel-Asp/Hubs/TerminalHub.cs b/LoongPanel-Asp/Hubs/TerminalHub.cs index 6391a7e..881a657 100644 --- a/LoongPanel-Asp/Hubs/TerminalHub.cs +++ b/LoongPanel-Asp/Hubs/TerminalHub.cs @@ -13,19 +13,25 @@ public class TerminalHub(SshStreamService sshStreamService):Hub await Groups.AddToGroupAsync(Context.ConnectionId, userId); } //create a terminal - public Task CreateTerminal(string serverId) + public void CreateTerminal(string serverId) { var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; sshStreamService.Connect(userId, serverId); - return Task.CompletedTask; } //send a message to the terminal - public async Task SendMessage(string message) + public void SendMessage(string message) { var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; Console.WriteLine(message); sshStreamService.Write(userId,message); } + + public void ResizeTerminal(int row, int col) + { + var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; + sshStreamService.ReSize(userId,col,row); + } + //断开 public override Task OnDisconnectedAsync(Exception? exception) { diff --git a/LoongPanel-Asp/Servers/SSHStreamService.cs b/LoongPanel-Asp/Servers/SSHStreamService.cs index f856544..60ed048 100644 --- a/LoongPanel-Asp/Servers/SSHStreamService.cs +++ b/LoongPanel-Asp/Servers/SSHStreamService.cs @@ -49,7 +49,28 @@ public class SshStreamService : IDisposable _sshStreams[userId]= (sshClient, shellStream); } - + + public void ReSize(string userId,int col, int row) + { + //从sshStreams中获取对象 + if (_sshStreams.TryGetValue(userId, out var tuple)) + { + var (ssh, stream) = tuple; + if (ssh.IsConnected) + { + var ansiResizeCommand = $"stty cols {col}; stty rows {row}"; + stream.WriteLine(ansiResizeCommand); + } + else + { + throw new InvalidOperationException("SSH client is not connected or shell stream is not available."); + } + } + else + { + throw new InvalidOperationException("SSH connection not found for the given user ID."); + } + } public void Write(string userId, string data) { diff --git a/web/components/Term.vue b/web/components/Term.vue index fa1f5fe..9d2ee59 100644 --- a/web/components/Term.vue +++ b/web/components/Term.vue @@ -2,20 +2,36 @@ import {useMainLayoutStore} from "~/strores/UseMainLayoutStore"; import {HubConnectionBuilder} from "@microsoft/signalr"; import {FitAddon} from 'xterm-addon-fit' +import { ArrowDownRight } from 'lucide-vue-next'; //导入xtrem import {Terminal} from "@xterm/xterm"; import "@xterm/xterm/css/xterm.css"; +import _ from "lodash"; + var term: Terminal; -onMounted(()=>{ - term = new Terminal({rows:40,cols:100,smoothScrollDuration:100,scrollback:1000}); - term.open(document.getElementById("terminal") as HTMLElement); - const fitAddon = new FitAddon(); - term.loadAddon(fitAddon); - fitAddon.fit(); +const terminal = ref(null); +const resizeObserver =_.debounce(()=>{ + if(terminal.value===null) return; + const cols = Math.floor(terminal.value.clientWidth / 9); + const rows = Math.floor(terminal.value.clientHeight / 17); + term.resize(cols-2, rows-1) + connection.invoke("ResizeTerminal", rows-1, cols-2) term.focus(); +},500) +onMounted(()=>{ + term = new Terminal({smoothScrollDuration:100,scrollback:1000,theme:{ + background: '#00000000', + }}); + term.open(document.getElementById("terminal") as HTMLElement); + if(terminal.value===null) return; + const cols = Math.floor(terminal.value.clientWidth / 9); + const rows = Math.floor(terminal.value.clientHeight / 17); + term.resize(cols-2, rows-1) + connection.invoke("ResizeTerminal", rows-1, cols-2) + term.focus(); + //绑定输入事件 term.onData((data) => { - console.log(data) connection.invoke("SendMessage", data) .catch(err => { console.log("Error while sending message: " + err); @@ -34,6 +50,7 @@ const connection = new HubConnectionBuilder() .build(); onMounted(async () => { connection.on("ReceiveMessage", (message) => { + if(message.startsWith("stty cols")||message.startsWith("stty rows")) return; term.write(message) }) await connection.start() @@ -51,12 +68,49 @@ onMounted(async () => { connection.invoke("SendMessage","neofetch\n") },200) }) +const resize=()=>{ + + //监听鼠标位置 + const onMouseMove=(e:MouseEvent)=>{ + // 获取鼠标位置 + const x = e.clientX; + const y = e.clientY; + // 获取terminal的宽高 + if(terminal.value===null) return; + let width = x - terminal.value.getBoundingClientRect().left; + let height = y - terminal.value.getBoundingClientRect().top; + // 设置terminal的宽高 + terminal.value.style.width = `${width}px`; + terminal.value.style.height = `${height}px`; + } + document.addEventListener('mousemove', onMouseMove, { passive: true }); + // 监听鼠标抬起事件,用于停止监听鼠标移动 + const onMouseUp = () => { + if(terminal.value===null) return; + let width = terminal.value.getBoundingClientRect().width; + width = Math.round(width / 9) * 9+20; + let height = terminal.value.getBoundingClientRect().height; + height = Math.round(height / 17) * 17+20; + terminal.value.style.width = `${width}px`; + terminal.value.style.height = `${height}px`; + resizeObserver() + // 停止监听鼠标移动事件 + document.removeEventListener('mousemove', onMouseMove); + // 停止监听鼠标抬起事件 + document.removeEventListener('mouseup', onMouseUp); + }; + + // 监听鼠标抬起事件 + document.addEventListener('mouseup', onMouseUp, { passive: true }); +} +