feat: 快速发送 shell 命令.
This commit is contained in:
@@ -22,7 +22,6 @@
|
|||||||
import { createDefaultOptions } from './core';
|
import { createDefaultOptions } from './core';
|
||||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||||
import { useAppStore } from '@/store';
|
import { useAppStore } from '@/store';
|
||||||
import { language as shellLanguage } from 'monaco-editor/esm/vs/basic-languages/shell/shell.js';
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
@@ -83,48 +82,6 @@
|
|||||||
emits('update:modelValue', value);
|
emits('update:modelValue', value);
|
||||||
emits('change', value);
|
emits('change', value);
|
||||||
});
|
});
|
||||||
|
|
||||||
// FIXME test containerClass
|
|
||||||
// TODO
|
|
||||||
// 代码提示
|
|
||||||
monaco.languages.registerCompletionItemProvider('shell', {
|
|
||||||
provideCompletionItems() {
|
|
||||||
const suggestions: any = [];
|
|
||||||
// 这个keywords就是java.java文件中有的
|
|
||||||
shellLanguage.keywords?.forEach((item: any) => {
|
|
||||||
suggestions.push({
|
|
||||||
label: item,
|
|
||||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
||||||
insertText: item,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
shellLanguage.operators?.forEach((item: any) => {
|
|
||||||
suggestions.push({
|
|
||||||
label: item,
|
|
||||||
kind: monaco.languages.CompletionItemKind.Operator,
|
|
||||||
insertText: item,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
shellLanguage.builtinFunctions?.forEach((item: any) => {
|
|
||||||
suggestions.push({
|
|
||||||
label: item,
|
|
||||||
kind: monaco.languages.CompletionItemKind.Function,
|
|
||||||
insertText: item,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
shellLanguage.builtinVariables?.forEach((item: any) => {
|
|
||||||
suggestions.push({
|
|
||||||
label: item,
|
|
||||||
kind: monaco.languages.CompletionItemKind.Variable,
|
|
||||||
insertText: item,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
suggestions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
emits('editor-mounted', editor);
|
emits('editor-mounted', editor);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -166,10 +123,10 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
// 初始化
|
||||||
init();
|
onMounted(init);
|
||||||
});
|
|
||||||
|
|
||||||
|
// 卸载
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
editor?.dispose();
|
editor?.dispose();
|
||||||
editor = undefined;
|
editor = undefined;
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal v-model:visible="visible"
|
||||||
|
title-align="start"
|
||||||
|
:width="width"
|
||||||
|
:body-style="{padding: '16px 8px'}"
|
||||||
|
:top="80"
|
||||||
|
:title="title"
|
||||||
|
:align-center="false"
|
||||||
|
:draggable="true"
|
||||||
|
:esc-to-close="false"
|
||||||
|
:mask-closable="false"
|
||||||
|
:unmount-on-close="true"
|
||||||
|
@close="handleClose">
|
||||||
|
<div :style="{ width: '100%', 'height': height }">
|
||||||
|
<editor v-model="value"
|
||||||
|
language="shell"
|
||||||
|
:theme="dark ? 'vs-dark' : 'vs'" />
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'shellEditorModal'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import useVisible from '@/hooks/visible';
|
||||||
|
import * as monaco from 'monaco-editor';
|
||||||
|
import { language } from 'monaco-editor/esm/vs/basic-languages/shell/shell.js';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
width: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '60%'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: 'calc(100vh - 280px)'
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { visible, setVisible } = useVisible();
|
||||||
|
|
||||||
|
const title = ref<string>();
|
||||||
|
const value = ref<string | any>();
|
||||||
|
|
||||||
|
// 打开
|
||||||
|
const open = (editorValue: string | any, editorTitle: string) => {
|
||||||
|
title.value = editorTitle;
|
||||||
|
value.value = editorValue;
|
||||||
|
setVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取值
|
||||||
|
const getValue = () => {
|
||||||
|
return value.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ open, getValue });
|
||||||
|
|
||||||
|
// 关闭
|
||||||
|
const handleClose = () => {
|
||||||
|
setVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
// 代码提示
|
||||||
|
monaco.languages.registerCompletionItemProvider('shell', {
|
||||||
|
provideCompletionItems() {
|
||||||
|
const suggestions: any = [];
|
||||||
|
language.keywords?.forEach((item: any) => {
|
||||||
|
suggestions.push({
|
||||||
|
label: item,
|
||||||
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||||
|
insertText: item,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
language.builtins?.forEach((item: any) => {
|
||||||
|
suggestions.push({
|
||||||
|
label: item,
|
||||||
|
kind: monaco.languages.CompletionItemKind.Function,
|
||||||
|
insertText: item,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
suggestions: [...new Set(suggestions)],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(.arco-modal-title) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1
orion-ops-ui/src/env.d.ts
vendored
1
orion-ops-ui/src/env.d.ts
vendored
@@ -18,3 +18,4 @@ declare module 'monaco-editor';
|
|||||||
declare module 'monaco-editor/esm/vs/editor/editor.worker?worker'
|
declare module 'monaco-editor/esm/vs/editor/editor.worker?worker'
|
||||||
declare module 'monaco-editor/esm/vs/language/json/json.worker?worker'
|
declare module 'monaco-editor/esm/vs/language/json/json.worker?worker'
|
||||||
declare module 'monaco-editor/esm/vs/basic-languages/yaml/yaml.js';
|
declare module 'monaco-editor/esm/vs/basic-languages/yaml/yaml.js';
|
||||||
|
declare module 'monaco-editor/esm/vs/basic-languages/shell/shell.js';
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ body[terminal-theme='dark'] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// arco 亮色配色
|
// arco 亮色配色
|
||||||
body .host-layout {
|
body .host-layout, .arco-modal-container {
|
||||||
--color-white: #ffffff;
|
--color-white: #ffffff;
|
||||||
--color-black: #000000;
|
--color-black: #000000;
|
||||||
--color-border: rgb(var(--gray-3));
|
--color-border: rgb(var(--gray-3));
|
||||||
@@ -115,7 +115,8 @@ body .host-layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// arco 暗色配色
|
// arco 暗色配色
|
||||||
body[terminal-theme='dark'] .host-layout {
|
body[terminal-theme='dark'] .host-layout,
|
||||||
|
body[terminal-theme='dark'] .arco-modal-container {
|
||||||
--color-white: rgba(255, 255, 255, 0.9);
|
--color-white: rgba(255, 255, 255, 0.9);
|
||||||
--color-black: #000000;
|
--color-black: #000000;
|
||||||
--color-border: #333335;
|
--color-border: #333335;
|
||||||
@@ -125,6 +126,7 @@ body[terminal-theme='dark'] .host-layout {
|
|||||||
--color-bg-4: #313132;
|
--color-bg-4: #313132;
|
||||||
--color-bg-5: #373739;
|
--color-bg-5: #373739;
|
||||||
--color-bg-white: #f6f6f6;
|
--color-bg-white: #f6f6f6;
|
||||||
|
--color-neutral-3: rgba(255, 255, 255, 0.12);
|
||||||
--color-text-1: rgba(255, 255, 255, 0.9);
|
--color-text-1: rgba(255, 255, 255, 0.9);
|
||||||
--color-text-2: rgba(255, 255, 255, 0.7);
|
--color-text-2: rgba(255, 255, 255, 0.7);
|
||||||
--color-text-3: rgba(255, 255, 255, 0.5);
|
--color-text-3: rgba(255, 255, 255, 0.5);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
:actions="rightActions"
|
:actions="rightActions"
|
||||||
position="bottom" />
|
position="bottom" />
|
||||||
<!-- 状态 -->
|
<!-- 状态 -->
|
||||||
<a-badge style="margin: 0 2px 0 8px;"
|
<a-badge class="status-bridge"
|
||||||
:status="getDictValue(connectStatusKey, session ? session.status : 0, 'status')"
|
:status="getDictValue(connectStatusKey, session ? session.status : 0, 'status')"
|
||||||
:text="getDictValue(connectStatusKey, session ? session.status : 0)" />
|
:text="getDictValue(connectStatusKey, session ? session.status : 0)" />
|
||||||
</div>
|
</div>
|
||||||
@@ -41,6 +41,13 @@
|
|||||||
}">
|
}">
|
||||||
<div class="terminal-inst" ref="terminalRef" />
|
<div class="terminal-inst" ref="terminalRef" />
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 命令编辑器 -->
|
||||||
|
<shell-editor-modal ref="modal"
|
||||||
|
:closable="false"
|
||||||
|
:body-style="{ padding: '16px 16px 16px 0' }"
|
||||||
|
:dark="themeSchema.dark"
|
||||||
|
cancel-text="关闭"
|
||||||
|
@ok="writeCommand(modal.getValue())" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -51,13 +58,15 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { SidebarAction } from '../../types/terminal.const';
|
||||||
import type { ITerminalSession, TerminalTabItem } from '../../types/terminal.type';
|
import type { ITerminalSession, TerminalTabItem } from '../../types/terminal.type';
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||||
import { useDictStore, useTerminalStore } from '@/store';
|
import { useDictStore, useTerminalStore } from '@/store';
|
||||||
import useCopy from '@/hooks/copy';
|
import useCopy from '@/hooks/copy';
|
||||||
import IconActions from '@/views/host/terminal/components/layout/icon-actions.vue';
|
import IconActions from '@/views/host/terminal/components/layout/icon-actions.vue';
|
||||||
import { connectStatusKey, SidebarAction } from '@/views/host/terminal/types/terminal.const';
|
import { connectStatusKey } from '../../types/terminal.const';
|
||||||
import { adjustColor } from '@/utils';
|
import { adjustColor } from '@/utils';
|
||||||
|
import ShellEditorModal from '@/components/view/shell-editor/shell-editor-modal.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tab: TerminalTabItem
|
tab: TerminalTabItem
|
||||||
@@ -67,27 +76,41 @@
|
|||||||
const { getDictValue } = useDictStore();
|
const { getDictValue } = useDictStore();
|
||||||
const { preference, sessionManager } = useTerminalStore();
|
const { preference, sessionManager } = useTerminalStore();
|
||||||
|
|
||||||
|
const modal = ref();
|
||||||
const commandInput = ref();
|
const commandInput = ref();
|
||||||
const themeSchema = preference.themeSchema;
|
const themeSchema = preference.themeSchema;
|
||||||
const terminalRef = ref();
|
const terminalRef = ref();
|
||||||
const session = ref<ITerminalSession>();
|
const session = ref<ITerminalSession>();
|
||||||
|
|
||||||
// FIXME
|
// FIXME
|
||||||
// 命令编辑器 搜索
|
// 卸载 最外层 terminal 组件, 卸载 style
|
||||||
|
// terminal themes 改成非同步 style
|
||||||
// (改成可配置/拆分)
|
// (改成可配置/拆分)
|
||||||
// 自定义 font siderBar 颜色, 集成到主题里面, 现在的问题是切换主题字体颜色就变了
|
// 自定义 font siderBar 颜色, 集成到主题里面, 现在的问题是切换主题字体颜色就变了
|
||||||
|
// 是否开启 link, url 匹配策略
|
||||||
|
// 是否开启 image
|
||||||
|
// search color 配置
|
||||||
// 右键菜单补充
|
// 右键菜单补充
|
||||||
|
// 搜索
|
||||||
// 搜索插件, link插件
|
// 搜索插件, link插件
|
||||||
|
// 截屏
|
||||||
|
|
||||||
// 发送命令
|
// 发送命令
|
||||||
const writeCommandInput = async (e: KeyboardEvent) => {
|
const writeCommandInput = async (e: KeyboardEvent) => {
|
||||||
const value = commandInput.value;
|
const value = commandInput.value;
|
||||||
if (value && e.code === 'F8' && session.value?.canWrite) {
|
if (value && e.code === 'F8') {
|
||||||
session.value.paste(value);
|
writeCommand(value);
|
||||||
commandInput.value = undefined;
|
commandInput.value = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 发送命令
|
||||||
|
const writeCommand = (value: string) => {
|
||||||
|
if (session.value?.canWrite) {
|
||||||
|
session.value.paste(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 右侧操作
|
// 右侧操作
|
||||||
const rightActions = computed<Array<SidebarAction>>(() => [
|
const rightActions = computed<Array<SidebarAction>>(() => [
|
||||||
{
|
{
|
||||||
@@ -140,6 +163,7 @@
|
|||||||
content: '命令编辑器',
|
content: '命令编辑器',
|
||||||
disabled: session.value?.canWrite,
|
disabled: session.value?.canWrite,
|
||||||
click: () => {
|
click: () => {
|
||||||
|
modal.value.open('', '');
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
icon: 'icon-search',
|
icon: 'icon-search',
|
||||||
@@ -269,6 +293,11 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-bridge {
|
||||||
|
margin: 0 2px 0 8px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal-wrapper {
|
.terminal-wrapper {
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import type { ITerminalChannel, ITerminalSession } from '../types/terminal.type';
|
import type { ITerminalChannel, ITerminalSession, TerminalAddons } from '../types/terminal.type';
|
||||||
import { useTerminalStore } from '@/store';
|
import { useTerminalStore } from '@/store';
|
||||||
import { fontFamilySuffix, TerminalStatus } from '../types/terminal.const';
|
import { fontFamilySuffix, TerminalStatus } from '../types/terminal.const';
|
||||||
import { InputProtocol } from '../types/terminal.protocol';
|
import { InputProtocol } from '../types/terminal.protocol';
|
||||||
import { ITerminalOptions, Terminal } from 'xterm';
|
import { ITerminalOptions, Terminal } from 'xterm';
|
||||||
import { FitAddon } from 'xterm-addon-fit';
|
import { FitAddon } from 'xterm-addon-fit';
|
||||||
import { WebglAddon } from 'xterm-addon-webgl';
|
import { WebglAddon } from 'xterm-addon-webgl';
|
||||||
|
import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||||
// 终端插件
|
import { SearchAddon } from 'xterm-addon-search';
|
||||||
export interface TerminalAddons {
|
import { ImageAddon } from 'xterm-addon-image';
|
||||||
fit: FitAddon;
|
|
||||||
webgl: WebglAddon;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 终端会话实现
|
// 终端会话实现
|
||||||
export default class TerminalSession implements ITerminalSession {
|
export default class TerminalSession implements ITerminalSession {
|
||||||
@@ -57,6 +54,9 @@ export default class TerminalSession implements ITerminalSession {
|
|||||||
// 注册插件
|
// 注册插件
|
||||||
this.addons.fit = new FitAddon();
|
this.addons.fit = new FitAddon();
|
||||||
this.addons.webgl = new WebglAddon();
|
this.addons.webgl = new WebglAddon();
|
||||||
|
this.addons.link = new WebLinksAddon();
|
||||||
|
this.addons.search = new SearchAddon();
|
||||||
|
this.addons.image = new ImageAddon();
|
||||||
for (const addon of Object.values(this.addons)) {
|
for (const addon of Object.values(this.addons)) {
|
||||||
this.inst.loadAddon(addon);
|
this.inst.loadAddon(addon);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import type { Terminal } from 'xterm';
|
import type { Terminal } from 'xterm';
|
||||||
|
import type { FitAddon } from 'xterm-addon-fit';
|
||||||
|
import type { WebglAddon } from 'xterm-addon-webgl';
|
||||||
|
import type { WebLinksAddon } from 'xterm-addon-web-links';
|
||||||
|
import type { SearchAddon } from 'xterm-addon-search';
|
||||||
|
import type { ImageAddon } from 'xterm-addon-image';
|
||||||
|
|
||||||
// 终端 tab 元素
|
// 终端 tab 元素
|
||||||
export interface TerminalTabItem {
|
export interface TerminalTabItem {
|
||||||
@@ -88,6 +93,15 @@ export interface ITerminalOutputProcessor {
|
|||||||
processOutput: (payload: OutputPayload) => void;
|
processOutput: (payload: OutputPayload) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 终端插件
|
||||||
|
export interface TerminalAddons {
|
||||||
|
fit: FitAddon;
|
||||||
|
webgl: WebglAddon;
|
||||||
|
link: WebLinksAddon;
|
||||||
|
search: SearchAddon;
|
||||||
|
image: ImageAddon;
|
||||||
|
}
|
||||||
|
|
||||||
// 终端会话定义
|
// 终端会话定义
|
||||||
export interface ITerminalSession {
|
export interface ITerminalSession {
|
||||||
hostId: number;
|
hostId: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user