✨ 支持cd全路径指令联动SFTP面板
This commit is contained in:
parent
324463d649
commit
2977e61ed5
@ -35,12 +35,12 @@ const pipeStream = (path, writeStream) => {
|
||||
}
|
||||
|
||||
function listenInput(sftpClient, socket) {
|
||||
socket.on('open_dir', async (path) => {
|
||||
socket.on('open_dir', async (path, tips = true) => {
|
||||
const exists = await sftpClient.exists(path)
|
||||
if(!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
|
||||
if(!exists) return socket.emit('not_exists_dir', tips ? '目录不存在或当前不可访问' : '')
|
||||
try {
|
||||
let dirLs = await sftpClient.list(path)
|
||||
socket.emit('dir_ls', dirLs)
|
||||
socket.emit('dir_ls', dirLs, path)
|
||||
} catch (error) {
|
||||
consola.error('open_dir Error', error.message)
|
||||
socket.emit('sftp_error', error.message)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.4",
|
||||
"description": "easynode-web",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -12,7 +12,8 @@
|
||||
>
|
||||
<template #header>
|
||||
<div class="title">
|
||||
{{ FileName }} - <span>{{ status }}</span>
|
||||
{{ filename }}
|
||||
<!-- {{ filename }} - <span>{{ status }}</span> -->
|
||||
</div>
|
||||
</template>
|
||||
<codemirror
|
||||
|
@ -1,19 +1,24 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
// import HostList from '@views/list/index.vue'
|
||||
// import Login from '@views/login/index.vue'
|
||||
// import terminal from '@views/terminal/index.vue'
|
||||
// import test from '@views/test/index.vue'
|
||||
import Login from '@views/login/index.vue'
|
||||
import Container from '@views/index.vue'
|
||||
import Server from '@views/server/index.vue'
|
||||
import Terminal from '@views/terminal/index.vue'
|
||||
import Credentials from '@views/credentials/index.vue'
|
||||
import Group from '@views/group/index.vue'
|
||||
import Onekey from '@views/onekey/index.vue'
|
||||
import Scripts from '@views/scripts/index.vue'
|
||||
import Setting from '@views/setting/index.vue'
|
||||
|
||||
const Login = () => import('@views/login/index.vue')
|
||||
const Container = () => import('@views/index.vue')
|
||||
const Server = () => import('@views/server/index.vue')
|
||||
const Terminal = () => import('@views/terminal/index.vue')
|
||||
const Credentials = () => import('@views/credentials/index.vue')
|
||||
const Group = () => import('@views/group/index.vue')
|
||||
const Onekey = () => import('@views/onekey/index.vue')
|
||||
const Scripts = () => import('@views/scripts/index.vue')
|
||||
const Setting = () => import('@views/setting/index.vue')
|
||||
// const Login = () => import('@views/login/index.vue')
|
||||
// const Container = () => import('@views/index.vue')
|
||||
// const Server = () => import('@views/server/index.vue')
|
||||
// const Terminal = () => import('@views/terminal/index.vue')
|
||||
// const Credentials = () => import('@views/credentials/index.vue')
|
||||
// const Group = () => import('@views/group/index.vue')
|
||||
// const Onekey = () => import('@views/onekey/index.vue')
|
||||
// const Scripts = () => import('@views/scripts/index.vue')
|
||||
// const Setting = () => import('@views/setting/index.vue')
|
||||
|
||||
const routes = [
|
||||
{ path: '/login', component: Login },
|
||||
|
@ -281,12 +281,17 @@ const connectSftp = () => {
|
||||
}
|
||||
|
||||
const listenSftp = () => {
|
||||
socket.value.on('dir_ls', (dirLs) => {
|
||||
socket.value.on('dir_ls', (dirLs, path) => {
|
||||
childDir.value = sortDirTree(dirLs)
|
||||
childDirLoading.value = false
|
||||
// 格式化path为当前目录
|
||||
let formatPath = path.split('/').filter(item => item)
|
||||
formatPath.unshift('/')
|
||||
// console.log('formatPath:', formatPath)
|
||||
paths.value = formatPath
|
||||
})
|
||||
socket.value.on('not_exists_dir', (errMsg) => {
|
||||
$message.error(errMsg)
|
||||
if (errMsg) $message.error(errMsg)
|
||||
childDirLoading.value = false
|
||||
})
|
||||
socket.value.on('rm_success', (res) => {
|
||||
@ -517,16 +522,20 @@ const uploadSliceFile = (fileInfo) => {
|
||||
})
|
||||
}
|
||||
|
||||
const openDir = () => {
|
||||
const openDir = (path = '', tips = true) => {
|
||||
childDirLoading.value = true
|
||||
curTarget.value = null
|
||||
socket.value.emit('open_dir', curPath.value)
|
||||
socket.value.emit('open_dir', path || curPath.value, tips)
|
||||
}
|
||||
|
||||
const getPath = (name = '') => {
|
||||
return curPath.value.length === 1 ? `/${ name }` : `${ curPath.value }/${ name }`
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openDir
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -22,7 +22,7 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['inputCommand',])
|
||||
const emit = defineEmits(['inputCommand', 'cdCommand',])
|
||||
|
||||
const socket = ref(null)
|
||||
const term = ref(null)
|
||||
@ -194,14 +194,76 @@ const onSelectionChange = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const terminalText = ref(null)
|
||||
const enterTimer = ref(null)
|
||||
|
||||
function filterAnsiSequences(str) {
|
||||
// 使用正则表达式移除ANSI转义序列
|
||||
// return str.replace(/\x1b\[[0-9;]*m|\x1b\[?[\d;]*[A-HJKSTfmin]/g, '')
|
||||
// eslint-disable-next-line
|
||||
return str.replace(/\x1b\[[0-9;]*[mGK]|(\x1b\][0-?]*[0-7;]*\x07)|(\x1b[\[\]()#%;][0-9;?]*[0-9A-PRZcf-ntqry=><])/g, '')
|
||||
}
|
||||
|
||||
// 处理 Backspace,删除前一个字符
|
||||
function applyBackspace(text) {
|
||||
let result = []
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
if (text[i] === '\b') {
|
||||
if (result.length > 0) {
|
||||
result.pop()
|
||||
}
|
||||
} else {
|
||||
result.push(text[i])
|
||||
}
|
||||
}
|
||||
return result.join('')
|
||||
}
|
||||
|
||||
function extractLastCdPath(text) {
|
||||
const regex = /cd\s+([^\s]+)(?=\s|$)/g
|
||||
let lastMatch
|
||||
let match
|
||||
regex.lastIndex = 0
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
lastMatch = match
|
||||
}
|
||||
return lastMatch ? lastMatch[1] : null
|
||||
}
|
||||
|
||||
const onData = () => {
|
||||
socket.value.on('output', (str) => {
|
||||
term.value.write(str)
|
||||
terminalText.value += str
|
||||
// console.log(terminalText.value)
|
||||
})
|
||||
term.value.onData((key) => {
|
||||
let acsiiCode = key.codePointAt()
|
||||
if (acsiiCode === 22) return handlePaste()
|
||||
if (acsiiCode === 6) return searchBar.value.show()
|
||||
enterTimer.value = setTimeout(() => {
|
||||
if (enterTimer.value) clearTimeout(enterTimer.value)
|
||||
if (key === '\r') { // Enter
|
||||
let cleanText = applyBackspace(filterAnsiSequences(terminalText.value))
|
||||
const lines = cleanText.split('\n')
|
||||
// console.log('lines: ', lines)
|
||||
const lastLine = lines[lines.length - 1].trim()
|
||||
// console.log('lastLine: ', lastLine)
|
||||
// 截取最后一个提示符后的内容('$'或'#'后的内容)
|
||||
const commandStartIndex = lastLine.lastIndexOf('#') + 1
|
||||
const commandText = lastLine.substring(commandStartIndex).trim()
|
||||
// console.log('Processed command: ', commandText)
|
||||
// eslint-disable-next-line
|
||||
const cdPath = extractLastCdPath(commandText)
|
||||
|
||||
if (cdPath) {
|
||||
console.log('cd command path:', cdPath)
|
||||
let firstChar = cdPath.charAt(0)
|
||||
if (!['/',].includes(firstChar)) return console.log('err fullpath:', cdPath) // 后端依赖不支持 '~'
|
||||
emit('cdCommand', cdPath)
|
||||
}
|
||||
terminalText.value = ''
|
||||
}
|
||||
})
|
||||
emit('inputCommand', key)
|
||||
socket.value.emit('input', key)
|
||||
})
|
||||
|
@ -125,8 +125,14 @@
|
||||
ref="terminalRefs"
|
||||
:host="item.host"
|
||||
@input-command="terminalInput"
|
||||
@cd-command="cdCommand"
|
||||
/>
|
||||
<Sftp
|
||||
v-if="showSftp"
|
||||
ref="sftpRefs"
|
||||
:host="item.host"
|
||||
@resize="resizeTerminal"
|
||||
/>
|
||||
<Sftp v-if="showSftp" :host="item.host" @resize="resizeTerminal" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
@ -165,9 +171,10 @@ const emit = defineEmits(['closed', 'removeTab', 'add-host',])
|
||||
const showInputCommand = ref(false)
|
||||
const infoSideRef = ref(null)
|
||||
const terminalRefs = ref([])
|
||||
const sftpRefs = ref([])
|
||||
let activeTabIndex = ref(0)
|
||||
let visible = ref(true)
|
||||
let showSftp = ref(false)
|
||||
let showSftp = ref(localStorage.getItem('showSftp') === 'true')
|
||||
let mainHeight = ref('')
|
||||
let isSyncAllSession = ref(false)
|
||||
let hostFormVisible = ref(false)
|
||||
@ -233,6 +240,18 @@ const terminalInput = (command) => {
|
||||
})
|
||||
}
|
||||
|
||||
const cdCommand = (path) => {
|
||||
// console.log('cdCommand:', path)
|
||||
if (!showSftp.value) return
|
||||
if (isSyncAllSession.value) {
|
||||
sftpRefs.value.forEach(sftpRef => {
|
||||
sftpRef.openDir(path)
|
||||
})
|
||||
} else {
|
||||
sftpRefs.value[activeTabIndex.value].openDir(path, false)
|
||||
}
|
||||
}
|
||||
|
||||
const tabChange = async (index) => {
|
||||
await $nextTick()
|
||||
const curTerminalRef = terminalRefs.value[index]
|
||||
@ -253,6 +272,7 @@ watch(terminalTabsLen, () => {
|
||||
})
|
||||
|
||||
watch(showSftp, () => {
|
||||
localStorage.setItem('showSftp', showSftp.value)
|
||||
nextTick(() => {
|
||||
resizeTerminal()
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user