支持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) { function listenInput(sftpClient, socket) {
socket.on('open_dir', async (path) => { socket.on('open_dir', async (path, tips = true) => {
const exists = await sftpClient.exists(path) const exists = await sftpClient.exists(path)
if(!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问') if(!exists) return socket.emit('not_exists_dir', tips ? '目录不存在或当前不可访问' : '')
try { try {
let dirLs = await sftpClient.list(path) let dirLs = await sftpClient.list(path)
socket.emit('dir_ls', dirLs) socket.emit('dir_ls', dirLs, path)
} catch (error) { } catch (error) {
consola.error('open_dir Error', error.message) consola.error('open_dir Error', error.message)
socket.emit('sftp_error', error.message) socket.emit('sftp_error', error.message)

View File

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

View File

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

View File

@ -1,19 +1,24 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
// import HostList from '@views/list/index.vue' import Login from '@views/login/index.vue'
// import Login from '@views/login/index.vue' import Container from '@views/index.vue'
// import terminal from '@views/terminal/index.vue' import Server from '@views/server/index.vue'
// import test from '@views/test/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 Login = () => import('@views/login/index.vue')
const Container = () => import('@views/index.vue') // const Container = () => import('@views/index.vue')
const Server = () => import('@views/server/index.vue') // const Server = () => import('@views/server/index.vue')
const Terminal = () => import('@views/terminal/index.vue') // const Terminal = () => import('@views/terminal/index.vue')
const Credentials = () => import('@views/credentials/index.vue') // const Credentials = () => import('@views/credentials/index.vue')
const Group = () => import('@views/group/index.vue') // const Group = () => import('@views/group/index.vue')
const Onekey = () => import('@views/onekey/index.vue') // const Onekey = () => import('@views/onekey/index.vue')
const Scripts = () => import('@views/scripts/index.vue') // const Scripts = () => import('@views/scripts/index.vue')
const Setting = () => import('@views/setting/index.vue') // const Setting = () => import('@views/setting/index.vue')
const routes = [ const routes = [
{ path: '/login', component: Login }, { path: '/login', component: Login },

View File

@ -281,12 +281,17 @@ const connectSftp = () => {
} }
const listenSftp = () => { const listenSftp = () => {
socket.value.on('dir_ls', (dirLs) => { socket.value.on('dir_ls', (dirLs, path) => {
childDir.value = sortDirTree(dirLs) childDir.value = sortDirTree(dirLs)
childDirLoading.value = false 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) => { socket.value.on('not_exists_dir', (errMsg) => {
$message.error(errMsg) if (errMsg) $message.error(errMsg)
childDirLoading.value = false childDirLoading.value = false
}) })
socket.value.on('rm_success', (res) => { socket.value.on('rm_success', (res) => {
@ -517,16 +522,20 @@ const uploadSliceFile = (fileInfo) => {
}) })
} }
const openDir = () => { const openDir = (path = '', tips = true) => {
childDirLoading.value = true childDirLoading.value = true
curTarget.value = null curTarget.value = null
socket.value.emit('open_dir', curPath.value) socket.value.emit('open_dir', path || curPath.value, tips)
} }
const getPath = (name = '') => { const getPath = (name = '') => {
return curPath.value.length === 1 ? `/${ name }` : `${ curPath.value }/${ name }` return curPath.value.length === 1 ? `/${ name }` : `${ curPath.value }/${ name }`
} }
defineExpose({
openDir
})
</script> </script>
<style lang="scss" scoped> <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 socket = ref(null)
const term = 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 = () => { const onData = () => {
socket.value.on('output', (str) => { socket.value.on('output', (str) => {
term.value.write(str) term.value.write(str)
terminalText.value += str
// console.log(terminalText.value)
}) })
term.value.onData((key) => { term.value.onData((key) => {
let acsiiCode = key.codePointAt() let acsiiCode = key.codePointAt()
if (acsiiCode === 22) return handlePaste() if (acsiiCode === 22) return handlePaste()
if (acsiiCode === 6) return searchBar.value.show() 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) emit('inputCommand', key)
socket.value.emit('input', key) socket.value.emit('input', key)
}) })

View File

@ -125,8 +125,14 @@
ref="terminalRefs" ref="terminalRefs"
:host="item.host" :host="item.host"
@input-command="terminalInput" @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> </div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -165,9 +171,10 @@ const emit = defineEmits(['closed', 'removeTab', 'add-host',])
const showInputCommand = ref(false) const showInputCommand = ref(false)
const infoSideRef = ref(null) const infoSideRef = ref(null)
const terminalRefs = ref([]) const terminalRefs = ref([])
const sftpRefs = ref([])
let activeTabIndex = ref(0) let activeTabIndex = ref(0)
let visible = ref(true) let visible = ref(true)
let showSftp = ref(false) let showSftp = ref(localStorage.getItem('showSftp') === 'true')
let mainHeight = ref('') let mainHeight = ref('')
let isSyncAllSession = ref(false) let isSyncAllSession = ref(false)
let hostFormVisible = 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) => { const tabChange = async (index) => {
await $nextTick() await $nextTick()
const curTerminalRef = terminalRefs.value[index] const curTerminalRef = terminalRefs.value[index]
@ -253,6 +272,7 @@ watch(terminalTabsLen, () => {
}) })
watch(showSftp, () => { watch(showSftp, () => {
localStorage.setItem('showSftp', showSftp.value)
nextTick(() => { nextTick(() => {
resizeTerminal() resizeTerminal()
}) })