From 8a7976a4ddc5baab8085c3dea8cc1e2b630c4600 Mon Sep 17 00:00:00 2001 From: lijiahang Date: Wed, 20 Mar 2024 15:28:20 +0800 Subject: [PATCH] =?UTF-8?q?:hammer:=20=E6=89=A7=E8=A1=8C=E6=97=A5=E5=BF=97?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- orion-ops-ui/src/api/exec/exec.ts | 2 +- .../src/components/app/navbar/index.vue | 1 + .../xtrem/log-appender/appender.const.ts | 35 ++- .../xtrem/log-appender/log-appender.ts | 150 ++++++++++-- .../xtrem/search-modal/index.vue} | 22 +- orion-ops-ui/src/utils/file.ts | 2 +- .../components/log-panel-view.vue | 230 +++++++++++++++--- .../exec-command/components/log-panel.vue | 5 +- .../host/terminal/components/ssh/ssh-view.vue | 20 +- 9 files changed, 392 insertions(+), 75 deletions(-) rename orion-ops-ui/src/{views/host/terminal/components/ssh/ssh-search-modal.vue => components/xtrem/search-modal/index.vue} (88%) diff --git a/orion-ops-ui/src/api/exec/exec.ts b/orion-ops-ui/src/api/exec/exec.ts index 5dc20f11..ce1803d2 100644 --- a/orion-ops-ui/src/api/exec/exec.ts +++ b/orion-ops-ui/src/api/exec/exec.ts @@ -93,5 +93,5 @@ export function getExecLogTailToken(request: ExecTailRequest) { * 下载执行日志文件 */ export function downloadExecLogFile(id: number) { - return axios.get('/asset/exec/download-log', { unwrap: true, params: { id } }); + return axios.get('/asset/exec/download-log', { unwrap: true, params: { id } }); } diff --git a/orion-ops-ui/src/components/app/navbar/index.vue b/orion-ops-ui/src/components/app/navbar/index.vue index 23111879..4dbfd895 100644 --- a/orion-ops-ui/src/components/app/navbar/index.vue +++ b/orion-ops-ui/src/components/app/navbar/index.vue @@ -332,6 +332,7 @@ display: flex; align-items: center; padding-left: 20px; + color: var(--color-text-1); &-logo { width: 32px; diff --git a/orion-ops-ui/src/components/xtrem/log-appender/appender.const.ts b/orion-ops-ui/src/components/xtrem/log-appender/appender.const.ts index 3ea6a03d..4f7809da 100644 --- a/orion-ops-ui/src/components/xtrem/log-appender/appender.const.ts +++ b/orion-ops-ui/src/components/xtrem/log-appender/appender.const.ts @@ -33,6 +33,7 @@ export interface LogDomRef { export interface LogAppenderConf { id: number; el: HTMLElement; + fixed: boolean; terminal: Terminal; addons: LogAddons; } @@ -49,8 +50,38 @@ export interface ILogAppender { // 初始化 init(refs: Array): Promise; - // 自适应 - fit(): void; + // 设置当前元素 + setCurrent(id: number): void; + + // 查找关键字 + find(word: string, next: boolean, options: any): void; + + // 聚焦 + focus(): void; + + // 设置固定 + setFixed(fixed: boolean): void; + + // 去顶部 + toTop(): void; + + // 去底部 + toBottom(): void; + + // 添加字体大小 + addFontSize(addSize: number): void; + + // 复制 + copy(): void; + + // 复制全部 + copyAll(): void; + + // 选中全部 + selectAll(): void; + + // 清空 + clear(): void; // 关闭 client closeClient(): void; diff --git a/orion-ops-ui/src/components/xtrem/log-appender/log-appender.ts b/orion-ops-ui/src/components/xtrem/log-appender/log-appender.ts index bc49c09b..7dc440e2 100644 --- a/orion-ops-ui/src/components/xtrem/log-appender/log-appender.ts +++ b/orion-ops-ui/src/components/xtrem/log-appender/log-appender.ts @@ -1,38 +1,38 @@ -import type { ExecTailRequest } from '@/api/exec/exec'; -import { getExecLogTailToken } from '@/api/exec/exec'; import type { ILogAppender, LogAddons, LogAppenderConf, LogDomRef } from './appender.const'; +import type { ExecTailRequest } from '@/api/exec/exec'; import { AppenderOptions } from './appender.const'; +import { getExecLogTailToken } from '@/api/exec/exec'; import { webSocketBaseUrl } from '@/utils/env'; import { Message } from '@arco-design/web-vue'; import { createWebSocket } from '@/utils'; +import { useDebounceFn } from '@vueuse/core'; +import { addEventListen, removeEventListen } from '@/utils/event'; +import { copy as copyText } from '@/hooks/copy'; import { Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; import { SearchAddon } from 'xterm-addon-search'; import { CanvasAddon } from 'xterm-addon-canvas'; -import { useDebounceFn } from '@vueuse/core'; -import { addEventListen, removeEventListen } from '@/utils/event'; - -// todo SEARCH addon setfixed -// todo font-size totop copy tobottom selectall clear -// todo 批量执行的 warn // 执行日志 appender 实现 export default class LogAppender implements ILogAppender { - private config: ExecTailRequest; + private current: LogAppenderConf; private client?: WebSocket; + private readonly config: ExecTailRequest; + private readonly appenderRel: Record; private keepAliveTask?: number; - private readonly fitFn: () => {}; + private readonly fitAllFn: () => {}; constructor(config: ExecTailRequest) { + this.current = undefined as unknown as LogAppenderConf; this.config = config; this.appenderRel = {}; - this.fitFn = useDebounceFn(this.fit).bind(this); + this.fitAllFn = useDebounceFn(this.fitAll).bind(this); } // 初始化 @@ -49,6 +49,8 @@ export default class LogAppender implements ILogAppender { for (let logDomRef of logDomRefs) { // 初始化 terminal const terminal = new Terminal(AppenderOptions); + // 初始化快捷键 + this.initCustomKey(terminal); // 初始化插件 const addons = this.initAddons(terminal); // 打开终端 @@ -57,12 +59,46 @@ export default class LogAppender implements ILogAppender { addons.fit.fit(); this.appenderRel[logDomRef.id] = { ...logDomRef, + fixed: false, terminal, addons }; } + // 设置当前对象 + this.current = this.appenderRel[logDomRefs[0].id]; // 注册自适应事件 - addEventListen(window, 'resize', this.fitFn); + addEventListen(window, 'resize', this.fitAllFn); + } + + // 初始化快捷键操作 + initCustomKey(terminal: Terminal) { + terminal.attachCustomKeyEventHandler((e: KeyboardEvent) => { + if (e.type !== 'keydown') { + return true; + } + if (e.ctrlKey && e.code === 'KeyC') { + // 复制 + e.preventDefault(); + this.copy(); + return false; + } else if (e.ctrlKey && e.code === 'KeyL') { + // 清空 + e.preventDefault(); + this.clear(); + return false; + } else if (e.ctrlKey && e.code === 'KeyA') { + // 全选 + e.preventDefault(); + this.selectAll(); + return false; + } else if (e.ctrlKey && e.code === 'KeyF') { + // 搜索 + e.preventDefault(); + // TODO open search + return false; + } + return true; + }); } // 初始化插件 @@ -102,10 +138,92 @@ export default class LogAppender implements ILogAppender { }, 15000); } - // 自适应 - fit(): void { + // 设置当前元素 + setCurrent(id: number): void { + const rel = this.appenderRel[id]; + if (!rel) { + return; + } + this.current = rel; + // 自适应 + rel.addons.fit.fit(); + // 非固定跳转到最底部 + if (!rel.fixed) { + rel.terminal.scrollToBottom(); + } + this.focus(); + } + + // 查找关键字 + find(word: string, next: boolean, options: any) { + if (next) { + this.current.addons.search.findNext(word, options); + } else { + this.current.addons.search.findPrevious(word, options); + } + } + + // 设置固定 + setFixed(fixed: boolean): void { + this.current.fixed = fixed; + this.focus(); + } + + // 去顶部 + toTop(): void { + this.current.terminal.scrollToTop(); + this.focus(); + } + + // 去底部 + toBottom(): void { + this.current.terminal.scrollToBottom(); + this.focus(); + } + + // 添加字体大小 + addFontSize(addSize: number): void { + this.current.terminal.options['fontSize'] = this.current.terminal.options['fontSize'] as number + addSize; + this.current.addons.fit.fit(); + this.focus(); + } + + // 复制 + copy(): void { + copyText(this.current.terminal.getSelection(), '已复制'); + this.focus(); + } + + // 复制全部 + copyAll(): void { + this.selectAll(); + this.copy(); + this.current.terminal.clearSelection(); + this.focus(); + } + + // 选中全部 + selectAll(): void { + this.current.terminal.selectAll(); + this.focus(); + } + + // 清空 + clear(): void { + this.current.terminal.clear(); + this.current.terminal.clearSelection(); + this.focus(); + } + + // 聚焦 + focus(): void { + this.current.terminal.focus(); + } + + // 自适应全部 + fitAll(): void { Object.values(this.appenderRel).forEach(s => { - s.addons?.fit?.fit(); + s.addons.fit.fit(); }); } @@ -129,7 +247,7 @@ export default class LogAppender implements ILogAppender { } }); // 移除自适应事件 - removeEventListen(window, 'resize', this.fitFn); + removeEventListen(window, 'resize', this.fitAllFn); } // 关闭 diff --git a/orion-ops-ui/src/views/host/terminal/components/ssh/ssh-search-modal.vue b/orion-ops-ui/src/components/xtrem/search-modal/index.vue similarity index 88% rename from orion-ops-ui/src/views/host/terminal/components/ssh/ssh-search-modal.vue rename to orion-ops-ui/src/components/xtrem/search-modal/index.vue index f3e408e5..156b5cdb 100644 --- a/orion-ops-ui/src/views/host/terminal/components/ssh/ssh-search-modal.vue +++ b/orion-ops-ui/src/components/xtrem/search-modal/index.vue @@ -53,7 +53,7 @@ @@ -130,26 +130,26 @@ display: flex; align-items: center; justify-content: space-between; - background: var(--search-bg); + background: var(--bg); transition: background-color .2s; &:focus-within, &:hover { - background: var(--search-bg-focus); + background: var(--bg-focus); .search-input { - color: var(--search-color-text-focus); + color: var(--color-text-focus); } .icon-wrapper { - color: var(--search-color-text-focus); + color: var(--color-text-focus); transition: background-color .2s; &:hover { - background: var(--search-bg-icon-hover-focus); + background: var(--bg-icon-hover-focus); } &.selected { - background: var(--search-bg-icon-selected-focus); + background: var(--bg-icon-selected-focus); } } } @@ -163,7 +163,7 @@ outline: none; height: 18px; font-size: 12px; - color: var(--search-color-text); + color: var(--color-text); } .word-option { @@ -183,18 +183,18 @@ display: flex; align-items: center; justify-content: center; - color: var(--search-color-text); + color: var(--color-text); &:not(:first-child) { margin-left: 2px; } &:hover { - background: var(--search-bg-icon-hover); + background: var(--bg-icon-hover); } &.selected { - background: var(--search-bg-icon-selected); + background: var(--bg-icon-selected); } } } diff --git a/orion-ops-ui/src/utils/file.ts b/orion-ops-ui/src/utils/file.ts index 0110282c..dcc80f41 100644 --- a/orion-ops-ui/src/utils/file.ts +++ b/orion-ops-ui/src/utils/file.ts @@ -87,7 +87,7 @@ export function getParentPath(path: string) { /** * 下载文件 */ -export function downloadFile(res: any, fileName: string) { +export function downloadFile(res: any, fileName: string = '') { const blob = new Blob([res.data]); const tempLink = document.createElement('a'); const blobURL = window.URL.createObjectURL(blob); diff --git a/orion-ops-ui/src/views/exec/exec-command/components/log-panel-view.vue b/orion-ops-ui/src/views/exec/exec-command/components/log-panel-view.vue index 40a0a068..e2417175 100644 --- a/orion-ops-ui/src/views/exec/exec-command/components/log-panel-view.vue +++ b/orion-ops-ui/src/views/exec/exec-command/components/log-panel-view.vue @@ -7,40 +7,159 @@
-
- - - - {{ getDictValue(execHostStatusKey, host.status) }} - - - - - {{ host.exitStatus }} - - - - - {{ formatDuration(host.startTime, host.finishTime) || '0s' }} - - -
+ + + + {{ getDictValue(execHostStatusKey, host.status) }} + + + + + {{ host.exitStatus }} + + + + + {{ formatDuration(host.startTime, host.finishTime) || '0s' }} + + -
TODO
-
- -
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + +
+ + +
@@ -56,10 +175,13 @@ import type { ExecCommandResponse } from '@/api/exec/exec'; import type { LogDomRef, ILogAppender } from '@/components/xtrem/log-appender/appender.const'; import { nextTick, ref, watch } from 'vue'; + import { downloadExecLogFile } from '@/api/exec/exec'; + import { downloadFile } from '@/utils/file'; import { formatDuration } from '@/utils'; import { execHostStatus, execHostStatusKey } from '@/views/exec/exec-log/types/const'; import { useDictStore } from '@/store'; import LogAppender from '@/components/xtrem/log-appender/log-appender'; + import XtermSearchModal from '@/components/xtrem/search-modal/index.vue'; import 'xterm/css/xterm.css'; const props = defineProps<{ @@ -71,11 +193,12 @@ const logRefs = ref>([]); const appender = ref(); + const searchModal = ref(); - // 切换标签自适应 - watch(() => props.current, () => { + // 切换标签 + watch(() => props.current, (val) => { nextTick(() => { - appender.value?.fit(); + appender.value?.setCurrent(val); }); }); @@ -107,10 +230,27 @@ }); }; + // 搜索关键字 + const searchWords = (word: string, next: boolean, options: any) => { + appender.value?.find(word, next, options); + }; + + // 关闭搜索框 + const searchClose = () => { + appender.value?.focus(); + }; + + // 下载文件 + const downloadLogFile = async (id: number) => { + const data = await downloadExecLogFile(id); + downloadFile(data); + }; + diff --git a/orion-ops-ui/src/views/exec/exec-command/components/log-panel.vue b/orion-ops-ui/src/views/exec/exec-command/components/log-panel.vue index 629f5d12..60eafd41 100644 --- a/orion-ops-ui/src/views/exec/exec-command/components/log-panel.vue +++ b/orion-ops-ui/src/views/exec/exec-command/components/log-panel.vue @@ -138,9 +138,10 @@ @host-real-width: @host-width + 16px; .log-panel-container { - width: 100%; - height: 100%; + width: calc(100% - 32px); + height: calc(100% - 32px); display: flex; + position: absolute; } .host-container, .log-container { diff --git a/orion-ops-ui/src/views/host/terminal/components/ssh/ssh-view.vue b/orion-ops-ui/src/views/host/terminal/components/ssh/ssh-view.vue index 5a43c4e5..a85534cf 100644 --- a/orion-ops-ui/src/views/host/terminal/components/ssh/ssh-view.vue +++ b/orion-ops-ui/src/views/host/terminal/components/ssh/ssh-view.vue @@ -44,9 +44,10 @@
- +
@@ -74,8 +75,8 @@ import { ActionBarItems, connectStatusKey } from '../../types/terminal.const'; import ShellEditorModal from '@/components/view/shell-editor/modal/index.vue'; import IconActions from '../layout/icon-actions.vue'; - import SshSearchModal from './ssh-search-modal.vue'; import SshContextMenu from './ssh-context-menu.vue'; + import XtermSearchModal from '@/components/xtrem/search-modal/index.vue'; const props = defineProps<{ tab: TerminalTabItem @@ -251,4 +252,15 @@ } } + .search-modal { + --bg-focus: var(--search-bg-focus); + --bg: var(--search-bg); + --color-text: var(--search-color-text); + --color-text-focus: var(--search-color-text-focus); + --bg-icon-hover: var(--search-bg-icon-hover); + --bg-icon-hover-focus: var(--search-bg-icon-hover-focus); + --bg-icon-selected: var(--search-bg-icon-selected); + --bg-icon-selected-focus: var(--search-bg-icon-selected-focus); + } +