This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
nezha-nezha-fronted/nezha-fronted/src/components/cli/consoleNew.vue

453 lines
15 KiB
Vue
Raw Normal View History

2022-12-09 09:22:38 +08:00
<template>
<div :id="'ternimalContainer'+idIndex" class="console" v-watermark="{show: showWatermark,text: waterMarkText, text1: terminal.host || '', textColor: 'rgba(215, 215, 215, 0.5)'}" v-clickoutside="clickHide">
<div :id="'terminal'+idIndex" style="height: 100%;width: 100%"></div>
2022-12-09 09:22:38 +08:00
<fileDirectory :host="host" v-clickoutside="closeFileDir" :uuid="terminal.uuid" v-show="fileDirectoryShow" @close="showFileDir(false)" :fileDirectoryShow="fileDirectoryShow" ref="fileDirectory"/>
<div
v-if="terminal.isLogin && showMenu"
class="terminal-menu menus"
:style="{
left: menuPosition.left + 'px',
top: menuPosition.top + 'px'
}"
>
<div class="terminal-menu-item">
<span @click="copySelection()"><i class="nz-icon nz-icon-override"/>{{$t('overall.duplicate')}}</span>
</div>
<div :disabled="!showPaste" class="terminal-menu-item" :class="showPaste ? '': 'terminal-menu-item-disabled'">
<span @click="paste()"> <i class="nz-icon nz-icon-putongwenjianlianjie"/> {{$t('project.topology.paste')}}</span>
</div>
<div class="terminal-menu-item">
<span @click="clearTerminal()"> <i class="nz-icon nz-icon-Clear"/> {{$t('terminal.clear')}}</span>
</div>
<div class="line"></div>
<div class="terminal-menu-item">
<span @click="showFileDir(true)"> <i class="nz-icon nz-icon-SFTP"/> {{$t('terminal.sftp')}}</span>
</div>
<div class="terminal-menu-item">
<span @click="closeTerminal(true)"><i class="nz-icon nz-icon-guanbi2"/> {{$t('overall.close')}}</span>
</div>
</div>
2022-12-09 09:22:38 +08:00
</div>
</template>
<script>
import Terminal from '../common/js/Xterm'
import { AttachAddon } from 'xterm-addon-attach'
import { FitAddon } from 'xterm-addon-fit'
import { CanvasAddon } from 'xterm-addon-canvas'
2022-12-09 09:22:38 +08:00
import fileDirectory from './fileDirectory'
export default {
name: 'console',
components: {
fileDirectory
},
props: {
terminal: { },
terminalType: { default: '1' },
idIndex: {
type: Number,
default: 0
},
isFullScreen: {
type: Boolean,
default: false
},
terminalSetting: {
type: Object,
default: () => {}
},
2022-12-09 09:22:38 +08:00
fontSize: {}
},
data () {
return {
term: null,
terminalSocket: null,
termAttachAddon: null,
termFitAddon: null,
2022-12-09 09:22:38 +08:00
termimalRows: 15,
termimalHeight: 270,
topMenuHeight: 30,
dragHeigh: 8,
minRow: 1,
fontSpaceHeight: 2, // 一行高度=fontSize+fontSpaceHeight
obj: {
id: 2
},
isInit: false,
successBackContent: 'Connecting to',
failBackContent: 'Sorry',
connectFailContent: 'Connection failed',
welcomeBackContent: 'Welcome',
psdCont: 'password: ',
conFinish: false,
conSuccessNum: 0,
inputSecret: false,
userName: '',
fileDirectoryShow: false,
2023-08-08 11:15:48 +08:00
host: '',
waterMarkText: 'Nezha',
showWatermark: false,
wordSeparator: "\\ :;~`!@#$%^&*()-=+|[]{}'\",.<>/?",
showMenu: false,
menuPosition: {
left: 0,
top: 0
},
showPaste: false
2022-12-09 09:22:38 +08:00
}
},
// computed: {
// userInfo () {
// return this.$store.getters.getUserInfo
// }
// },
2022-12-09 09:22:38 +08:00
watch: {
},
methods: {
prompt (term) {
term.write('\r\n ')// term.write('\r\n~$ ');
},
resize (consoleHeigt, consoleWidth) {
this.termFitAddon.fit()
2022-12-09 09:22:38 +08:00
this.resizeServiceConsole()
},
resizeServiceConsole () {
const consoleBox = document.getElementById('ternimalContainer' + this.idIndex)
const width = document.body.clientWidth - 10// 可视宽度
const height = parseInt(consoleBox.offsetHeight) - 10
2022-12-09 09:22:38 +08:00
const winStyle = {
width: width,
height: height,
2022-12-09 09:22:38 +08:00
cols: this.term.cols, // cols和rows在resizeConsole方法已经设置
rows: this.term.rows
}
this.$post('terminal/resize', winStyle).then(response => {
if (response.code === 200) {
// this.term.resize()
this.termFitAddon.fit()
2022-12-09 09:22:38 +08:00
} else {
this.$message.error(response.msg)
}
})
},
focusConsole () {
this.term.focus()
},
beforeCreate () {
let rows = this.termimalRows
// const consoleBox = document.getElementById('ternimalContainer' + this.idIndex)
const height = document.body.clientHeight// 高度
// consoleBox.style.height = `${height - 120}px`
rows = (height - this.topMenuHeight) / (this.fontSize + this.fontSpaceHeight)
rows = parseInt(rows)
const terminalContainer = document.getElementById('terminal' + this.idIndex)
this.term = new Terminal({
allowTransparency: true,
allowProposedApi: true,
overviewRulerWidth: 8,
2022-12-09 09:22:38 +08:00
cursorStyle: 'block', // 光标样式 null | 'block' | 'underline' | 'bar'
disableStdin: false, // 是否应禁用输入
fontSize: 18,
2023-08-08 11:15:48 +08:00
lineHeight: 1.2,
letterSpacing: 0,
background: 'transparent',
scrollback: this.terminalSetting.scrollbackLines,
wordSeparator: this.terminalSetting.wordSeparator
2022-12-09 09:22:38 +08:00
})
this.termFitAddon = new FitAddon()
this.term.loadAddon(this.termFitAddon)
// const attachAddon = new AttachAddon()
// this.term.loadAddon(attachAddon)
// this.term.loadAddon(fitAddon)
setTimeout(() => {
this.term.open(terminalContainer)
this.term.loadAddon(new CanvasAddon())
this.term.focus()
this.termFitAddon.fit()
this.create()
}, 100)
2022-12-09 09:22:38 +08:00
},
create () {
const that = this
const token = localStorage.getItem('nz-token')
let baseUrl = JSON.parse(JSON.stringify(this.$axios.defaults.baseURL))
const protocol = window.location.protocol.indexOf('https') > -1 ? 'wss' : 'ws'
if (baseUrl.startsWith('/')) {
baseUrl = `${protocol}://` + window.location.host + baseUrl
} else {
baseUrl = baseUrl.replace('http://', 'ws://').replace('https://', 'wss://')
}
let url = ''
this.terminal.height = document.body.clientHeight - 100
this.terminal.width = document.body.clientWidth
const userInfo = localStorage.getItem('nz-userInfo') ? JSON.parse(localStorage.getItem('nz-userInfo')) : ''
if (userInfo) {
this.waterMarkText = `${userInfo.username}(${userInfo.name})`
}
2022-12-09 09:22:38 +08:00
if (this.terminal.type === 'asset') {
url = baseUrl + '/terminal.ws?width=' + this.terminal.width + '&height=' + this.terminal.height + '&cols=' + this.term.cols + '&rows=' + this.term.rows + '&token=' + token + '&assetId=' + this.terminal.assetId + '&accountId=' + this.terminal.accountId + '&uuid=' + this.terminal.uuid
2022-12-09 09:22:38 +08:00
} else if (this.terminal.type === 'custom') {
url = baseUrl + '/terminal.ws?width=' + this.terminal.width + '&height=' + this.terminal.height + '&cols=' + this.term.cols + '&rows=' + this.term.rows + '&token=' + token + '&accountId=' + this.terminal.accountId + '&uuid=' + this.terminal.uuid
2022-12-09 09:22:38 +08:00
Object.keys(this.terminal.custom).forEach(key => {
if (this.terminal.custom[key]) {
url += '&' + key + '=' + this.terminal.custom[key]
}
})
}
this.terminalSocket = new WebSocket(url)
this.termAttachAddon = new AttachAddon(this.terminalSocket)
2022-12-09 09:22:38 +08:00
// 连接成功onclose
this.terminalSocket.onopen = () => {
this.terminal.isLogin = true
this.showMenu = false
2022-12-09 09:22:38 +08:00
this.isInit = true
this.term.focus()
2022-12-09 09:22:38 +08:00
}
// 个性化配置
this.initTerminalSetting()
2022-12-09 09:22:38 +08:00
// 返回
this.terminalSocket.onmessage = function (evt) {
let backContent = evt.data
const welComIndex = backContent.indexOf(that.welcomeBackContent)
if (welComIndex > -1) { // 无服务器信息只与nezha进行了连接
const connectResult = {
title: '',
color: 1
}
that.$emit('refreshConsoleTitle', connectResult)// 1:grey 2 green 3 red
} else {
const successContentIndex = backContent.indexOf(that.successBackContent)
if (successContentIndex > -1) {
// that.conFinish = true;
const startIndex = successContentIndex + that.successBackContent.length + 1
backContent = backContent.substring(startIndex)
const endIndex = backContent.indexOf('\r\n')
const title = backContent.substring(0, endIndex)
const connectResult = {
title: title,
color: 2
}
that.$emit('refreshConsoleTitle', connectResult)// 1:grey 2 green 3 red
} else { // 失败
const failContentIndex = backContent.indexOf(that.failBackContent)
const connectFailIndex = backContent.indexOf(that.connectFailContent)
if (failContentIndex > -1) {
// that.conFinish = true;
const connectResult = {
title: '',
color: 3
}
that.$emit('refreshConsoleTitle', connectResult)// 1:grey 2 green 3 red
} else if (connectFailIndex > -1) {
const connectResult = {
title: '',
color: 3
}
that.$emit('refreshConsoleTitle', connectResult)// 1:grey 2 green 3 red
}
}
}
}
// 关闭
this.terminalSocket.onclose = () => {
this.terminal.isLogin = false
// 报错sorry的还没来得及看信息就关闭
// this.$emit("closeConsole",this.terminal.name);//
this.term && (this.term.options.disableStdin = true)
2022-12-09 09:22:38 +08:00
}
// 错误
this.terminalSocket.onerror = (e) => {
this.terminal.isLogin = false
}
this.term.loadAddon(this.termAttachAddon)
2022-12-09 09:22:38 +08:00
this.term._initialized = true
// this.term.fit()// 自适应大小(使终端的尺寸和几何尺寸适合于终端容器的尺寸) 只是width
2022-12-09 09:22:38 +08:00
this.$nextTick(() => { // 解决进入全屏和退出全屏是底部隐藏
// this.setFontSize(this.fontSize)
2022-12-09 09:22:38 +08:00
})
},
closeSocket () {
if (this.terminalSocket) {
this.terminalSocket.close()
this.terminalSocket = ''
2022-12-09 09:22:38 +08:00
}
if (this.term) {
this.term.dispose()
2022-12-09 09:22:38 +08:00
}
// 初始化console的高度
this.conFinish = false
},
setFontSize (fontSize) {
this.term && (this.term.options.fontSize = fontSize)
2022-12-09 09:22:38 +08:00
const consoleBox = document.getElementById('ternimalContainer' + this.idIndex)
const width = document.body.clientWidth// 可视宽度
let height = parseInt(consoleBox.offsetHeight)
if (height == null || !height) { height = this.termimalHeight }
const winStyle = {
width: width,
height: height,
cols: this.term.cols, // cols和rows在resizeConsole方法已经设置
rows: this.term.rows
}
// 调整终端可视区域高度
// document.getElementsByClassName('xterm-screen')[this.idIndex].style.height = height - 30 + 'px'
this.$nextTick(() => {
this.term.resize(this.term.cols, this.term.rows)
this.$post('terminal/resize', winStyle).then(response => {
if (response.code === 200) {
this.termFitAddon.fit()
2022-12-09 09:22:38 +08:00
} else {
this.$message.error(response.msg)
}
})
})
},
reconnect () {
this.terminal.isLogin = false
this.closeSocket()
setTimeout(() => {
this.beforeCreate()
}, 100)
2022-12-09 09:22:38 +08:00
},
closeFileDir () {
this.showFileDir(false)
},
showFileDir (show) {
if (JSON.stringify(show) == JSON.stringify(this.fileDirectoryShow)) {
return
}
if (show) {
this.fileDirectoryShow = show
}
let animationClass = ''
animationClass = show ? 'backInRight' : 'backOutRight'
this.animateCSS(this.$refs.fileDirectory.$el, animationClass).then((message) => {
this.fileDirectoryShow = show
})
},
clearTerminal () {
this.term.clear()
},
closeTerminal () {
this.$emit('close')
},
enterStr (message) {
if (this.terminalSocket && this.terminal.isLogin) {
this.terminalSocket.send(message)
setTimeout(() => {
this.terminalSocket.send('\n')
}, 100)
this.term.scrollToBottom()
this.historyChange(message)
}
},
// 新增历史记录
historyChange (message) {
this.$emit('historyChange', message)
},
initTerminalSetting () { // 个性化配置
// 1 render
this.showWatermark = this.terminalSetting.watermark
this.term.onSelectionChange((p, a) => {
if (this.terminalSetting.copyOnSelect) {
setTimeout(() => {
this.copySelection()
})
}
})
// this.term.selectionManager._isCharWordSeparator = (charData) => {
// if (charData[2] === 0) {
// return false
// }
// return this.wordSeparator.indexOf(charData[1]) >= 0
// }
},
renderTerminalSetting () {
this.showWatermark = this.terminalSetting.watermark
this.wordSeparator = this.terminalSetting.wordSeparator
this.term.options = {
scrollback: this.terminalSetting.scrollbackLines,
wordSeparator: this.terminalSetting.wordSeparator
}
},
copySelection () {
let str = this.term.getSelection()
if (!this.terminalSetting.copyWithFormatting) {
str = str.replace(/[\r\n]/g, '')
}
if (this.terminalSetting.copyTrimEnd) {
str = str.replace(/(\s*$)/g, '')
}
this.$copyText(str).then((res) => {
console.log(res)
})
},
async contextmenu (event) {
event.preventDefault()
event.stopPropagation()
if (this.terminalSetting.rightClick === 'none') {
return
}
if (this.terminalSetting.rightClick === 'menu') {
this.tiggerMenu(true, event)
}
if (this.terminalSetting.rightClick === 'paste') {
if (!document.execCommand('paste')) {
const text = await navigator.clipboard.readText()
this.term.write(text)
}
}
},
async paste () {
if (!this.showPaste) {
return
}
if (!document.execCommand('paste')) {
const text = await navigator.clipboard.readText()
this.term.write(text)
}
},
tiggerMenu (flag, event) {
this.showMenu = false
if (flag) {
let top = 0
let left = 0
if (event.clientY < document.body.clientHeight / 2) {
top = event.clientY + 10
} else {
top = event.clientY - 180 - 10
}
if (event.clientX < document.body.clientWidth / 2) {
left = event.clientX + 10
} else {
left = event.clientX - 150 - 10
}
this.menuPosition = {
left: left,
top: top
}
}
this.showMenu = flag
},
clickHide () {
this.showMenu = false
2022-12-09 09:22:38 +08:00
}
},
mounted () {
this.beforeCreate()
this.showWatermark = this.terminalSetting.watermark
const dom = document.getElementById('ternimalContainer' + this.idIndex)
dom.addEventListener('contextmenu', this.contextmenu)
dom.addEventListener('click', this.clickHide)
this.showPaste = navigator.clipboard
2022-12-09 09:22:38 +08:00
},
beforeDestroy () {
this.closeSocket()
const dom = document.getElementById('ternimalContainer' + this.idIndex)
dom && dom.removeEventListener('contextmenu', this.contextmenu)
dom && dom.removeEventListener('click', this.clickHide)
2022-12-09 09:22:38 +08:00
}
}
</script>