支持cd全路径指令联动SFTP面板

This commit is contained in:
chaos-zhu 2024-08-12 12:34:41 +08:00
parent 324463d649
commit 2977e61ed5
7 changed files with 122 additions and 25 deletions

View File

@ -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)

View File

@ -1,6 +1,6 @@
{
"name": "web",
"version": "2.1.3",
"version": "2.1.4",
"description": "easynode-web",
"private": true,
"scripts": {

View File

@ -12,7 +12,8 @@
>
<template #header>
<div class="title">
{{ FileName }} - <span>{{ status }}</span>
{{ filename }}
<!-- {{ filename }} - <span>{{ status }}</span> -->
</div>
</template>
<codemirror

View File

@ -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 },

View File

@ -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>

View File

@ -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)
})

View File

@ -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()
})