✨ 支持快捷脚本&简化客户端安装脚本
This commit is contained in:
parent
af9f762c25
commit
0c6ea82be5
10
README.md
10
README.md
@ -81,16 +81,10 @@ pm2 start index.js --name easynode-server
|
|||||||
|
|
||||||
- 占用端口:**22022**
|
- 占用端口:**22022**
|
||||||
|
|
||||||
#### X86架构
|
> 安装
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
wget -qO- --no-check-certificate https://mirror.ghproxy.com/https://raw.githubusercontent.com/chaos-zhu/easynode/main/client/easynode-client-install-x86.sh | bash
|
wget -qO- --no-check-certificate https://mirror.ghproxy.com/https://raw.githubusercontent.com/chaos-zhu/easynode/main/client/easynode-client-instal.sh | bash
|
||||||
```
|
|
||||||
|
|
||||||
#### ARM架构
|
|
||||||
|
|
||||||
```shell
|
|
||||||
wget -qO- --no-check-certificate https://mirror.ghproxy.com/https://raw.githubusercontent.com/chaos-zhu/easynode/main/client/easynode-client-install-arm.sh | bash
|
|
||||||
```
|
```
|
||||||
|
|
||||||
> 卸载
|
> 卸载
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [ "$(id -u)" != "0" ] ; then
|
|
||||||
echo "***********************请切换到root再尝试执行***********************"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
SERVER_NAME=easynode-client
|
|
||||||
FILE_PATH=/root/local/easynode-client
|
|
||||||
SERVICE_PATH=/etc/systemd/system
|
|
||||||
SERVER_VERSION=v1.0
|
|
||||||
|
|
||||||
echo "***********************开始安装 easynode-client_${SERVER_VERSION}***********************"
|
|
||||||
|
|
||||||
systemctl status ${SERVER_NAME} > /dev/null 2>&1
|
|
||||||
if [ $? != 4 ]
|
|
||||||
then
|
|
||||||
echo "***********************停用旧服务***********************"
|
|
||||||
systemctl stop ${SERVER_NAME}
|
|
||||||
systemctl disable ${SERVER_NAME}
|
|
||||||
systemctl daemon-reload
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "${SERVICE_PATH}/${SERVER_NAME}.service" ]
|
|
||||||
then
|
|
||||||
echo "***********************移除旧服务***********************"
|
|
||||||
chmod 777 ${SERVICE_PATH}/${SERVER_NAME}.service
|
|
||||||
rm -Rf ${SERVICE_PATH}/${SERVER_NAME}.service
|
|
||||||
systemctl daemon-reload
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d ${FILE_PATH} ]
|
|
||||||
then
|
|
||||||
echo "***********************移除旧文件***********************"
|
|
||||||
chmod 777 ${FILE_PATH}
|
|
||||||
rm -Rf ${FILE_PATH}
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 开始安装
|
|
||||||
|
|
||||||
echo "***********************创建文件PATH***********************"
|
|
||||||
mkdir -p ${FILE_PATH}
|
|
||||||
|
|
||||||
echo "***********************下载开始***********************"
|
|
||||||
DOWNLOAD_FILE_URL="https://mirror.ghproxy.com/https://github.com/chaos-zhu/easynode/releases/download/v2.0.0/easynode-client-x86"
|
|
||||||
DOWNLOAD_SERVICE_URL="https://mirror.ghproxy.com/https://github.com/chaos-zhu/easynode/releases/download/v2.0.0/easynode-client.service"
|
|
||||||
|
|
||||||
# -O 指定路径和文件名(这里是二进制文件, 不需要扩展名)
|
|
||||||
wget -O ${FILE_PATH}/${SERVER_NAME} --no-check-certificate --no-cache ${DOWNLOAD_FILE_URL}
|
|
||||||
if [ $? != 0 ]
|
|
||||||
then
|
|
||||||
echo "***********************下载${SERVER_NAME}失败***********************"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
wget -O ${FILE_PATH}/${SERVER_NAME}.service --no-check-certificate --no-cache ${DOWNLOAD_SERVICE_URL}
|
|
||||||
|
|
||||||
if [ $? != 0 ]
|
|
||||||
then
|
|
||||||
echo "***********************下载${SERVER_NAME}.service失败***********************"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "***********************下载成功***********************"
|
|
||||||
|
|
||||||
# echo "***********************设置权限***********************"
|
|
||||||
chmod +x ${FILE_PATH}/${SERVER_NAME}
|
|
||||||
chmod 777 ${FILE_PATH}/${SERVER_NAME}.service
|
|
||||||
|
|
||||||
# echo "***********************移动service&reload***********************"
|
|
||||||
mv ${FILE_PATH}/${SERVER_NAME}.service ${SERVICE_PATH}
|
|
||||||
|
|
||||||
# echo "***********************daemon-reload***********************"
|
|
||||||
systemctl daemon-reload
|
|
||||||
|
|
||||||
echo "***********************准备启动服务***********************"
|
|
||||||
systemctl start ${SERVER_NAME}
|
|
||||||
|
|
||||||
if [ $? != 0 ]
|
|
||||||
then
|
|
||||||
echo "***********************${SERVER_NAME}.service启动失败***********************"
|
|
||||||
echo "***********************可能是服务器开启了SELinux, 参见Q&A***********************"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "***********************服务启动成功***********************"
|
|
||||||
|
|
||||||
# echo "***********************设置开机启动***********************"
|
|
||||||
systemctl enable ${SERVER_NAME}
|
|
||||||
|
|
||||||
echo "***********************安装成功***********************"
|
|
@ -42,9 +42,19 @@ echo "***********************创建文件PATH***********************"
|
|||||||
mkdir -p ${FILE_PATH}
|
mkdir -p ${FILE_PATH}
|
||||||
|
|
||||||
echo "***********************下载开始***********************"
|
echo "***********************下载开始***********************"
|
||||||
DOWNLOAD_FILE_URL="https://mirror.ghproxy.com/https://github.com/chaos-zhu/easynode/releases/download/v2.0.0/easynode-client-arm64"
|
|
||||||
DOWNLOAD_SERVICE_URL="https://mirror.ghproxy.com/https://github.com/chaos-zhu/easynode/releases/download/v2.0.0/easynode-client.service"
|
DOWNLOAD_SERVICE_URL="https://mirror.ghproxy.com/https://github.com/chaos-zhu/easynode/releases/download/v2.0.0/easynode-client.service"
|
||||||
|
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
|
||||||
|
if [ "$ARCH" = "x86_64" ] ; then
|
||||||
|
DOWNLOAD_FILE_URL="https://mirror.ghproxy.com/https://github.com/chaos-zhu/easynode/releases/download/v2.0.0/easynode-client-x86"
|
||||||
|
elif [ "$ARCH" = "aarch64" ] ; then
|
||||||
|
DOWNLOAD_FILE_URL="https://mirror.ghproxy.com/https://github.com/chaos-zhu/easynode/releases/download/v2.0.0/easynode-client-arm64"
|
||||||
|
else
|
||||||
|
echo "未知的架构:$ARCH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# -O 指定路径和文件名(这里是二进制文件, 不需要扩展名)
|
# -O 指定路径和文件名(这里是二进制文件, 不需要扩展名)
|
||||||
wget -O ${FILE_PATH}/${SERVER_NAME} --no-check-certificate --no-cache ${DOWNLOAD_FILE_URL}
|
wget -O ${FILE_PATH}/${SERVER_NAME} --no-check-certificate --no-cache ${DOWNLOAD_FILE_URL}
|
||||||
if [ $? != 0 ]
|
if [ $? != 0 ]
|
@ -14,6 +14,7 @@ module.exports = {
|
|||||||
notifyConfDBPath: path.join(process.cwd(),'app/db/notify.db'),
|
notifyConfDBPath: path.join(process.cwd(),'app/db/notify.db'),
|
||||||
groupConfDBPath: path.join(process.cwd(),'app/db/group.db'),
|
groupConfDBPath: path.join(process.cwd(),'app/db/group.db'),
|
||||||
emailNotifyDBPath: path.join(process.cwd(),'app/db/email.db'),
|
emailNotifyDBPath: path.join(process.cwd(),'app/db/email.db'),
|
||||||
|
scriptsDBPath: path.join(process.cwd(),'app/db/scripts.db'),
|
||||||
apiPrefix: '/api/v1',
|
apiPrefix: '/api/v1',
|
||||||
logConfig: {
|
logConfig: {
|
||||||
outDir: path.join(process.cwd(),'./app/logs'),
|
outDir: path.join(process.cwd(),'./app/logs'),
|
||||||
|
@ -16,7 +16,7 @@ const addGroupList = async ({ res, request }) => {
|
|||||||
let group = { name, index }
|
let group = { name, index }
|
||||||
groupList.push(group)
|
groupList.push(group)
|
||||||
await writeGroupList(groupList)
|
await writeGroupList(groupList)
|
||||||
res.success({ data: '新增成功' })
|
res.success({ data: '添加成功' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateGroupList = async ({ res, request }) => {
|
const updateGroupList = async ({ res, request }) => {
|
||||||
|
@ -57,7 +57,7 @@ async function updateHost({
|
|||||||
name: newName, host: newHost, index, expired, expiredNotify, group, consoleUrl, remark,
|
name: newName, host: newHost, index, expired, expiredNotify, group, consoleUrl, remark,
|
||||||
port, username, authType, password, privateKey, credential, command
|
port, username, authType, password, privateKey, credential, command
|
||||||
}
|
}
|
||||||
if (!hostList.some(({ host }) => host === oldHost)) return res.fail({ msg: `原实例[${ oldHost }]不存在,请尝试新增实例` })
|
if (!hostList.some(({ host }) => host === oldHost)) return res.fail({ msg: `原实例[${ oldHost }]不存在,请尝试添加实例` })
|
||||||
|
|
||||||
let idx = hostList.findIndex(({ host }) => host === oldHost)
|
let idx = hostList.findIndex(({ host }) => host === oldHost)
|
||||||
const oldRecord = hostList[idx]
|
const oldRecord = hostList[idx]
|
||||||
|
52
server/app/controller/scripts.js
Normal file
52
server/app/controller/scripts.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
const { readScriptList, writeScriptList } = require('../utils')
|
||||||
|
|
||||||
|
async function getScriptList({ res }) {
|
||||||
|
let data = await readScriptList()
|
||||||
|
data = data.map(item => {
|
||||||
|
return { ...item, id: item._id }
|
||||||
|
})
|
||||||
|
data?.sort((a, b) => Number(b.index || 0) - Number(a.index || 0))
|
||||||
|
res.success({ data })
|
||||||
|
}
|
||||||
|
|
||||||
|
const addScript = async ({ res, request }) => {
|
||||||
|
let { body: { name, remark, content, index } } = request
|
||||||
|
if (!name || !content) return res.fail({ data: false, msg: '参数错误' })
|
||||||
|
index = Number(index) || 0
|
||||||
|
let scriptsList = await readScriptList()
|
||||||
|
let record = { name, remark, content, index }
|
||||||
|
scriptsList.push(record)
|
||||||
|
await writeScriptList(scriptsList)
|
||||||
|
res.success({ data: '添加成功' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateScriptList = async ({ res, request }) => {
|
||||||
|
let { params: { id } } = request
|
||||||
|
let { body: { name, remark, content, index } } = request
|
||||||
|
if (!name || !content) return res.fail({ data: false, msg: '参数错误' })
|
||||||
|
let scriptsList = await readScriptList()
|
||||||
|
let idx = scriptsList.findIndex(item => item._id === id)
|
||||||
|
if (idx === -1) return res.fail({ data: false, msg: `脚本ID${ id }不存在` })
|
||||||
|
const { _id } = scriptsList[idx]
|
||||||
|
let record = Object.assign({ _id }, { name, remark, content, index })
|
||||||
|
scriptsList.splice(idx, 1, record)
|
||||||
|
await writeScriptList(scriptsList)
|
||||||
|
res.success({ data: '修改成功' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeScript = async ({ res, request }) => {
|
||||||
|
let { params: { id } } = request
|
||||||
|
let scriptsList = await readScriptList()
|
||||||
|
let idx = scriptsList.findIndex(item => item._id === id)
|
||||||
|
if (idx === -1) return res.fail({ msg: '脚本ID不存在' })
|
||||||
|
scriptsList.splice(idx, 1)
|
||||||
|
await writeScriptList(scriptsList)
|
||||||
|
res.success({ data: '移除成功' })
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
addScript,
|
||||||
|
getScriptList,
|
||||||
|
updateScriptList,
|
||||||
|
removeScript
|
||||||
|
}
|
@ -27,7 +27,7 @@ const addSSH = async ({ res, request }) => {
|
|||||||
|
|
||||||
sshRecord.push({ ...record, date: Date.now() })
|
sshRecord.push({ ...record, date: Date.now() })
|
||||||
await writeSSHRecord(sshRecord)
|
await writeSSHRecord(sshRecord)
|
||||||
consola.info('新增凭证:', name)
|
consola.info('添加凭证:', name)
|
||||||
res.success({ data: '保存成功' })
|
res.success({ data: '保存成功' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ const { getHostList, addHost, updateHost, removeHost, importHost } = require('..
|
|||||||
const { login, getpublicKey, updatePwd, getLoginRecord } = require('../controller/user')
|
const { login, getpublicKey, updatePwd, getLoginRecord } = require('../controller/user')
|
||||||
const { getSupportEmailList, getUserEmailList, updateUserEmailList, removeUserEmail, pushEmail, getNotifyList, updateNotifyList } = require('../controller/notify')
|
const { getSupportEmailList, getUserEmailList, updateUserEmailList, removeUserEmail, pushEmail, getNotifyList, updateNotifyList } = require('../controller/notify')
|
||||||
const { getGroupList, addGroupList, updateGroupList, removeGroup } = require('../controller/group')
|
const { getGroupList, addGroupList, updateGroupList, removeGroup } = require('../controller/group')
|
||||||
|
const { getScriptList, addScript, updateScriptList, removeScript } = require('../controller/scripts')
|
||||||
|
|
||||||
const ssh = [
|
const ssh = [
|
||||||
{
|
{
|
||||||
@ -141,4 +142,27 @@ const group = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
module.exports = [].concat(ssh, host, user, notify, group)
|
const scripts = [
|
||||||
|
{
|
||||||
|
method: 'get',
|
||||||
|
path: '/script',
|
||||||
|
controller: getScriptList
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
path: '/script',
|
||||||
|
controller: addScript
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'delete',
|
||||||
|
path: '/script/:id',
|
||||||
|
controller: removeScript
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'put',
|
||||||
|
path: '/script/:id',
|
||||||
|
controller: updateScriptList
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
module.exports = [].concat(ssh, host, user, notify, group, scripts)
|
||||||
|
@ -37,7 +37,7 @@ async function getClientsInfo(socketId) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.on('connect_error', (error) => {
|
.on('connect_error', (error) => {
|
||||||
consola.error('client connect fail:', host, name, error.message)
|
// consola.error('client connect fail:', host, name, error.message)
|
||||||
clientsData[host] = null
|
clientsData[host] = null
|
||||||
})
|
})
|
||||||
.on('disconnect', () => {
|
.on('disconnect', () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const Datastore = require('@seald-io/nedb')
|
const Datastore = require('@seald-io/nedb')
|
||||||
const { credentialsDBPath, hostListDBPath, keyDBPath, emailNotifyDBPath, notifyConfDBPath, groupConfDBPath } = require('../config')
|
const { credentialsDBPath, hostListDBPath, keyDBPath, emailNotifyDBPath, notifyConfDBPath, groupConfDBPath, scriptsDBPath } = require('../config')
|
||||||
|
|
||||||
module.exports.KeyDB = class KeyDB {
|
module.exports.KeyDB = class KeyDB {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -66,3 +66,14 @@ module.exports.EmailNotifyDB = class EmailNotifyDB {
|
|||||||
return EmailNotifyDB.instance
|
return EmailNotifyDB.instance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports.ScriptsDB = class ScriptsDB {
|
||||||
|
constructor() {
|
||||||
|
if (!ScriptsDB.instance) {
|
||||||
|
ScriptsDB.instance = new Datastore({ filename: scriptsDBPath, autoload: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getInstance() {
|
||||||
|
return ScriptsDB.instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,7 +12,9 @@ const {
|
|||||||
getNotifySwByType,
|
getNotifySwByType,
|
||||||
writeNotifyList,
|
writeNotifyList,
|
||||||
readGroupList,
|
readGroupList,
|
||||||
writeGroupList
|
writeGroupList,
|
||||||
|
readScriptList,
|
||||||
|
writeScriptList
|
||||||
} = require('./storage')
|
} = require('./storage')
|
||||||
const { RSADecryptSync, AESEncryptSync, AESDecryptSync, SHA1Encrypt } = require('./encrypt')
|
const { RSADecryptSync, AESEncryptSync, AESDecryptSync, SHA1Encrypt } = require('./encrypt')
|
||||||
const { verifyAuthSync, isProd } = require('./verify-auth')
|
const { verifyAuthSync, isProd } = require('./verify-auth')
|
||||||
@ -47,5 +49,7 @@ module.exports = {
|
|||||||
getNotifySwByType,
|
getNotifySwByType,
|
||||||
writeNotifyList,
|
writeNotifyList,
|
||||||
readGroupList,
|
readGroupList,
|
||||||
writeGroupList
|
writeGroupList,
|
||||||
|
readScriptList,
|
||||||
|
writeScriptList
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const { KeyDB, HostListDB, SshRecordDB, NotifyDB, GroupDB, EmailNotifyDB } = require('./db-class')
|
const { KeyDB, HostListDB, SshRecordDB, NotifyDB, GroupDB, EmailNotifyDB, ScriptsDB } = require('./db-class')
|
||||||
|
|
||||||
const readKey = async () => {
|
const readKey = async () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -234,6 +234,42 @@ const writeGroupList = async (list = []) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const readScriptList = async () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const scriptsDB = new ScriptsDB().getInstance()
|
||||||
|
scriptsDB.find({}, (err, docs) => {
|
||||||
|
if (err) {
|
||||||
|
consola.error('读取scripts list错误: ', err)
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
resolve(docs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeScriptList = async (list = []) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const scriptsDB = new ScriptsDB().getInstance()
|
||||||
|
scriptsDB.remove({}, { multi: true }, (err) => {
|
||||||
|
if (err) {
|
||||||
|
consola.error('清空group list出错:', err)
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
scriptsDB.insert(list, (err, newDocs) => {
|
||||||
|
if (err) {
|
||||||
|
consola.error('写入新的group list出错:', err)
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
scriptsDB.compactDatafile()
|
||||||
|
resolve(newDocs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
readSSHRecord,
|
readSSHRecord,
|
||||||
writeSSHRecord,
|
writeSSHRecord,
|
||||||
@ -248,5 +284,7 @@ module.exports = {
|
|||||||
writeGroupList,
|
writeGroupList,
|
||||||
readSupportEmailList,
|
readSupportEmailList,
|
||||||
readUserEmailList,
|
readUserEmailList,
|
||||||
writeUserEmailList
|
writeUserEmailList,
|
||||||
|
readScriptList,
|
||||||
|
writeScriptList
|
||||||
}
|
}
|
@ -87,5 +87,17 @@ export default {
|
|||||||
},
|
},
|
||||||
deleteGroup(id) {
|
deleteGroup(id) {
|
||||||
return axios({ url: `/group/${ id }`, method: 'delete' })
|
return axios({ url: `/group/${ id }`, method: 'delete' })
|
||||||
|
},
|
||||||
|
getScriptList() {
|
||||||
|
return axios({ url: '/script', method: 'get' })
|
||||||
|
},
|
||||||
|
addScript(data) {
|
||||||
|
return axios({ url: '/script', method: 'post', data })
|
||||||
|
},
|
||||||
|
updateScript(id, data) {
|
||||||
|
return axios({ url: `/script/${ id }`, method: 'put', data })
|
||||||
|
},
|
||||||
|
deleteScript(id) {
|
||||||
|
return axios({ url: `/script/${ id }`, method: 'delete' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,16 +62,16 @@ let menuList = reactive([
|
|||||||
icon: markRaw(FolderOpened),
|
icon: markRaw(FolderOpened),
|
||||||
index: '/group'
|
index: '/group'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: '脚本库',
|
||||||
|
icon: markRaw(ArrowRight),
|
||||||
|
index: '/scripts'
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// name: '批量指令',
|
// name: '批量指令',
|
||||||
// icon: markRaw(Pointer),
|
// icon: markRaw(Pointer),
|
||||||
// index: '/onekey'
|
// index: '/onekey'
|
||||||
// },
|
// },
|
||||||
// {
|
|
||||||
// name: '脚本库',
|
|
||||||
// icon: markRaw(ArrowRight),
|
|
||||||
// index: '/scripts'
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
name: '系统设置',
|
name: '系统设置',
|
||||||
icon: markRaw(Setting),
|
icon: markRaw(Setting),
|
||||||
|
@ -10,6 +10,7 @@ const useStore = defineStore({
|
|||||||
hostList: [],
|
hostList: [],
|
||||||
groupList: [],
|
groupList: [],
|
||||||
sshList: [],
|
sshList: [],
|
||||||
|
scriptList: [],
|
||||||
HostStatusSocket: null,
|
HostStatusSocket: null,
|
||||||
user: localStorage.getItem('user') || null,
|
user: localStorage.getItem('user') || null,
|
||||||
token: sessionStorage.getItem('token') || localStorage.getItem('token') || null,
|
token: sessionStorage.getItem('token') || localStorage.getItem('token') || null,
|
||||||
@ -37,6 +38,7 @@ const useStore = defineStore({
|
|||||||
await this.getGroupList()
|
await this.getGroupList()
|
||||||
await this.getHostList()
|
await this.getHostList()
|
||||||
await this.getSSHList()
|
await this.getSSHList()
|
||||||
|
await this.getScriptList()
|
||||||
},
|
},
|
||||||
async getHostList() {
|
async getHostList() {
|
||||||
const { data: hostList } = await $api.getHostList()
|
const { data: hostList } = await $api.getHostList()
|
||||||
@ -54,6 +56,11 @@ const useStore = defineStore({
|
|||||||
// console.log('sshList:', sshList)
|
// console.log('sshList:', sshList)
|
||||||
this.$patch({ sshList })
|
this.$patch({ sshList })
|
||||||
},
|
},
|
||||||
|
async getScriptList() {
|
||||||
|
const { data: scriptList } = await $api.getScriptList()
|
||||||
|
// console.log('scriptList:', scriptList)
|
||||||
|
this.$patch({ scriptList })
|
||||||
|
},
|
||||||
getHostPing() {
|
getHostPing() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.hostList.forEach((item) => {
|
this.hostList.forEach((item) => {
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<span>
|
<span>
|
||||||
<el-button @click="sshFormVisible = false">关闭</el-button>
|
<el-button @click="sshFormVisible = false">关闭</el-button>
|
||||||
<el-button type="primary" @click="updateForm">{{ isModify ? '修改' : '新增' }}</el-button>
|
<el-button type="primary" @click="updateForm">{{ isModify ? '修改' : '添加' }}</el-button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<span>
|
<span>
|
||||||
<el-button @click="groupFormVisible = false">关闭</el-button>
|
<el-button @click="groupFormVisible = false">关闭</el-button>
|
||||||
<el-button type="primary" @click="updateForm">{{ isModify ? '修改' : '新增' }}</el-button>
|
<el-button type="primary" @click="updateForm">{{ isModify ? '修改' : '添加' }}</el-button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
@ -1,19 +1,175 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="">
|
<div class="scripts_container">
|
||||||
开发中...
|
<div class="header">
|
||||||
|
<el-button type="primary" @click="addScript">添加脚本</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table v-loading="loading" :data="scriptList">
|
||||||
|
<el-table-column prop="index" label="序号" />
|
||||||
|
<el-table-column prop="name" label="名称" />
|
||||||
|
<el-table-column prop="remark" label="备注" />
|
||||||
|
<el-table-column prop="content" label="脚本内容" />
|
||||||
|
<el-table-column label="操作">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" @click="handleChange(row)">修改</el-button>
|
||||||
|
<el-button v-show="row.id !== 'own'" type="danger" @click="handleRemove(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-dialog
|
||||||
|
v-model="formVisible"
|
||||||
|
width="600px"
|
||||||
|
top="150px"
|
||||||
|
:title="isModify ? '修改脚本' : '添加脚本'"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="clearFormInfo"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="updateFormRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="rules"
|
||||||
|
:hide-required-asterisk="true"
|
||||||
|
label-suffix=":"
|
||||||
|
label-width="100px"
|
||||||
|
:show-message="false"
|
||||||
|
>
|
||||||
|
<el-form-item label="脚本名称" prop="name">
|
||||||
|
<el-input
|
||||||
|
v-model.trim="formData.name"
|
||||||
|
clearable
|
||||||
|
placeholder=""
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="脚本备注" prop="remark">
|
||||||
|
<el-input
|
||||||
|
v-model.trim="formData.remark"
|
||||||
|
clearable
|
||||||
|
placeholder=""
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="脚本序号" prop="index">
|
||||||
|
<el-input
|
||||||
|
v-model.trim.number="formData.index"
|
||||||
|
clearable
|
||||||
|
placeholder=""
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="content" label="脚本内容" show-overflow-tooltip>
|
||||||
|
<el-input
|
||||||
|
v-model.trim="formData.content"
|
||||||
|
type="textarea"
|
||||||
|
:rows="5"
|
||||||
|
clearable
|
||||||
|
autocomplete="off"
|
||||||
|
style="margin-top: 5px;"
|
||||||
|
placeholder="shell script"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span>
|
||||||
|
<el-button @click="formVisible = false">关闭</el-button>
|
||||||
|
<el-button type="primary" @click="updateForm">{{ isModify ? '修改' : '添加' }}</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { ref, reactive, computed, nextTick, getCurrentInstance } from 'vue'
|
||||||
|
|
||||||
|
const { proxy: { $api, $message, $messageBox, $store } } = getCurrentInstance()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const formVisible = ref(false)
|
||||||
|
let isModify = ref(false)
|
||||||
|
|
||||||
|
let formData = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
data() {
|
remark: '',
|
||||||
return {
|
index: 0,
|
||||||
}
|
content: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = computed(() => {
|
||||||
|
return {
|
||||||
|
name: { required: true, trigger: 'change' },
|
||||||
|
remark: { required: false, trigger: 'change' },
|
||||||
|
index: { required: false, type: 'number', trigger: 'change' },
|
||||||
|
content: { required: true, trigger: 'change' }
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateFormRef = ref(null)
|
||||||
|
|
||||||
|
let scriptList = computed(() => $store.scriptList)
|
||||||
|
|
||||||
|
let addScript = () => {
|
||||||
|
formData.id = null
|
||||||
|
isModify.value = false
|
||||||
|
formVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleChange = (row) => {
|
||||||
|
Object.assign(formData, { ...row })
|
||||||
|
formVisible.value = true
|
||||||
|
isModify.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateForm() {
|
||||||
|
updateFormRef.value.validate()
|
||||||
|
.then(async () => {
|
||||||
|
let data = { ...formData }
|
||||||
|
if (isModify.value) {
|
||||||
|
await $api.updateScript(data.id, data)
|
||||||
|
} else {
|
||||||
|
await $api.addScript(data)
|
||||||
|
}
|
||||||
|
formVisible.value = false
|
||||||
|
await $store.getScriptList()
|
||||||
|
$message.success('success')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearFormInfo = () => {
|
||||||
|
nextTick(() => updateFormRef.value.resetFields())
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRemove = ({ id, name }) => {
|
||||||
|
$messageBox.confirm(`确认删除该脚本:${ name }`, 'Warning', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
await $api.deleteScript(id)
|
||||||
|
await $store.getScriptList()
|
||||||
|
$message.success('success')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.scripts_container {
|
||||||
|
padding: 20px;
|
||||||
|
.header {
|
||||||
|
padding: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
.host_count {
|
||||||
|
display: block;
|
||||||
|
width: 100px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #87cf63;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
@ -288,7 +288,7 @@ const visible = computed({
|
|||||||
set: (newVal) => emit('update:show', newVal)
|
set: (newVal) => emit('update:show', newVal)
|
||||||
})
|
})
|
||||||
|
|
||||||
const title = computed(() => props.defaultData ? '修改实例' : '新增实例')
|
const title = computed(() => props.defaultData ? '修改实例' : '添加实例')
|
||||||
|
|
||||||
let groupList = computed(() => $store.groupList)
|
let groupList = computed(() => $store.groupList)
|
||||||
let sshList = computed(() => $store.sshList)
|
let sshList = computed(() => $store.sshList)
|
||||||
|
@ -190,7 +190,7 @@
|
|||||||
</el-button> -->
|
</el-button> -->
|
||||||
<el-button
|
<el-button
|
||||||
:type="inputCommandStyle ? 'primary' : 'success'"
|
:type="inputCommandStyle ? 'primary' : 'success'"
|
||||||
style="display: block;width: 80%;margin: 30px auto;"
|
style="display: block;width: 80%;margin: 15px auto;"
|
||||||
@click="clickInputCommand"
|
@click="clickInputCommand"
|
||||||
>
|
>
|
||||||
命令输入框
|
命令输入框
|
||||||
|
@ -26,7 +26,7 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['input',])
|
const emit = defineEmits(['inputCommand',])
|
||||||
|
|
||||||
const socket = ref(null)
|
const socket = ref(null)
|
||||||
const term = ref(null)
|
const term = ref(null)
|
||||||
@ -206,8 +206,7 @@ const onData = () => {
|
|||||||
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()
|
||||||
emit('input', { idx: props.index, key })
|
emit('inputCommand', key)
|
||||||
// console.log('input:', key)
|
|
||||||
socket.value.emit('input', key)
|
socket.value.emit('input', key)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -218,8 +217,7 @@ const handleClear = () => {
|
|||||||
|
|
||||||
const handlePaste = async () => {
|
const handlePaste = async () => {
|
||||||
let key = await navigator.clipboard.readText()
|
let key = await navigator.clipboard.readText()
|
||||||
emit('input', { idx: props.index, key })
|
emit('inputCommand', key)
|
||||||
// console.log('input:', key)
|
|
||||||
socket.value.emit('input', key)
|
socket.value.emit('input', key)
|
||||||
term.value.focus()
|
term.value.focus()
|
||||||
}
|
}
|
||||||
@ -231,7 +229,7 @@ const focusTab = () => {
|
|||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleInputCommand = (command) => {
|
const inputCommand = (command) => {
|
||||||
socket.value.emit('input', command)
|
socket.value.emit('input', command)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,7 +248,7 @@ onBeforeUnmount(() => {
|
|||||||
defineExpose({
|
defineExpose({
|
||||||
focusTab,
|
focusTab,
|
||||||
handleResize,
|
handleResize,
|
||||||
handleInputCommand,
|
inputCommand,
|
||||||
handleClear
|
handleClear
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,34 +1,99 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="terminal_wrap">
|
<div class="terminal_wrap">
|
||||||
<div class="terminal_top">
|
<div class="terminal_top">
|
||||||
<el-dropdown trigger="click">
|
<div class="left_menu">
|
||||||
<span class="link_text">新建连接<el-icon><arrow-down /></el-icon></span>
|
<el-dropdown trigger="click">
|
||||||
<template #dropdown>
|
<span class="link_text">新建连接<el-icon><arrow-down /></el-icon></span>
|
||||||
<el-dropdown-menu>
|
<template #dropdown>
|
||||||
<el-dropdown-item v-for="(item, index) in hostList" :key="index" @click="handleCommandHost(item)">
|
<el-dropdown-menu>
|
||||||
{{ item.name }} {{ item.host }}
|
<el-dropdown-item v-for="(item, index) in hostList" :key="index" @click="handleCommandHost(item)">
|
||||||
</el-dropdown-item>
|
{{ item.name }} {{ item.host }}
|
||||||
</el-dropdown-menu>
|
</el-dropdown-item>
|
||||||
</template>
|
</el-dropdown-menu>
|
||||||
</el-dropdown>
|
</template>
|
||||||
<el-dropdown trigger="click">
|
</el-dropdown>
|
||||||
<span class="link_text">会话同步<el-icon><arrow-down /></el-icon></span>
|
<!-- <el-dropdown trigger="click">
|
||||||
<template #dropdown>
|
<span class="link_text">会话同步<el-icon><arrow-down /></el-icon></span>
|
||||||
<el-dropdown-menu>
|
<template #dropdown>
|
||||||
<el-dropdown-item @click="handleSyncSession">
|
<el-dropdown-menu>
|
||||||
<el-icon v-show="isSyncAllSession"><Select class="action_icon" /></el-icon>
|
<el-dropdown-item @click="handleSyncSession">
|
||||||
<span>同步键盘输入到所有会话</span>
|
<el-icon v-show="isSyncAllSession"><Select class="action_icon" /></el-icon>
|
||||||
</el-dropdown-item>
|
<span>同步键盘输入到所有会话</span>
|
||||||
<!-- <el-dropdown-item @click="handleSyncSession">
|
</el-dropdown-item>
|
||||||
同步键盘输入到部分会话
|
</el-dropdown-menu>
|
||||||
</el-dropdown-item> -->
|
</template>
|
||||||
</el-dropdown-menu>
|
</el-dropdown> -->
|
||||||
</template>
|
<el-dropdown
|
||||||
</el-dropdown>
|
trigger="click"
|
||||||
<!-- <div class="link_text fullscreen" @click="handleFullScreen">全屏</div> -->
|
max-height="50vh"
|
||||||
<el-icon class="full_icon">
|
:teleported="false"
|
||||||
<FullScreen class="icon" @click="handleFullScreen" />
|
class="scripts_menu"
|
||||||
</el-icon>
|
>
|
||||||
|
<span class="link_text">快捷命令<el-icon><arrow-down /></el-icon></span>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item v-for="item in scriptList" :key="item.id" @click="handleExecScript(item)">
|
||||||
|
<span>{{ item.name }}</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
<el-dropdown trigger="click">
|
||||||
|
<span class="link_text">设置<el-icon><arrow-down /></el-icon></span>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item @click="handleFullScreen">
|
||||||
|
<span>开启全屏</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item disabled @click="handleFullScreen">
|
||||||
|
<span>终端设置(开发中)</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
<!-- <el-dropdown trigger="click">
|
||||||
|
<span class="link_text">设置
|
||||||
|
<el-icon class="hidden_icon"><arrow-down /></el-icon>
|
||||||
|
</span>
|
||||||
|
</el-dropdown> -->
|
||||||
|
</div>
|
||||||
|
<div class="right_overview">
|
||||||
|
<div class="switch_wrap">
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
content="开启后发送键盘输入到所有会话"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-switch
|
||||||
|
v-model="isSyncAllSession"
|
||||||
|
class="swtich"
|
||||||
|
inline-prompt
|
||||||
|
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
|
||||||
|
active-text="同步"
|
||||||
|
inactive-text="同步"
|
||||||
|
/>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="switch_wrap">
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
content="SFTP文件传输"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-switch
|
||||||
|
v-model="showSftp"
|
||||||
|
class="swtich"
|
||||||
|
inline-prompt
|
||||||
|
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
|
||||||
|
active-text="SFTP"
|
||||||
|
inactive-text="SFTP"
|
||||||
|
/>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<!-- <el-icon class="full_icon">
|
||||||
|
<FullScreen class="icon" @click="handleFullScreen" />
|
||||||
|
</el-icon> -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info_box">
|
<div class="info_box">
|
||||||
<InfoSide
|
<InfoSide
|
||||||
@ -56,12 +121,12 @@
|
|||||||
>
|
>
|
||||||
<div class="tab_content_wrap" :style="{ height: mainHeight + 'px' }">
|
<div class="tab_content_wrap" :style="{ height: mainHeight + 'px' }">
|
||||||
<TerminalTab
|
<TerminalTab
|
||||||
ref="terminalTabRefs"
|
ref="terminalRefs"
|
||||||
:index="index"
|
:index="index"
|
||||||
:host="item.host"
|
:host="item.host"
|
||||||
@input="terminalInput"
|
@input-command="terminalInput"
|
||||||
/>
|
/>
|
||||||
<Sftp :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>
|
||||||
@ -98,9 +163,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 terminalTabRefs = ref([])
|
const terminalRefs = ref([])
|
||||||
let activeTabIndex = ref(0)
|
let activeTabIndex = ref(0)
|
||||||
let visible = ref(true)
|
let visible = ref(true)
|
||||||
|
let showSftp = ref(false)
|
||||||
let mainHeight = ref('')
|
let mainHeight = ref('')
|
||||||
let isSyncAllSession = ref(false)
|
let isSyncAllSession = ref(false)
|
||||||
let hostFormVisible = ref(false)
|
let hostFormVisible = ref(false)
|
||||||
@ -110,6 +176,7 @@ const terminalTabs = computed(() => props.terminalTabs)
|
|||||||
const terminalTabsLen = computed(() => props.terminalTabs.length)
|
const terminalTabsLen = computed(() => props.terminalTabs.length)
|
||||||
const curHost = computed(() => terminalTabs.value[activeTabIndex.value])
|
const curHost = computed(() => terminalTabs.value[activeTabIndex.value])
|
||||||
let hostList = computed(() => $store.hostList)
|
let hostList = computed(() => $store.hostList)
|
||||||
|
let scriptList = computed(() => $store.scriptList)
|
||||||
|
|
||||||
// const closable = computed(() => terminalTabs.length > 1)
|
// const closable = computed(() => terminalTabs.length > 1)
|
||||||
|
|
||||||
@ -135,7 +202,7 @@ const handleUpdateList = async ({ isConfig, host }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleResizeTerminalSftp() {
|
const handleResizeTerminalSftp = () => {
|
||||||
$nextTick(() => {
|
$nextTick(() => {
|
||||||
mainHeight.value = document.querySelector('.terminals_sftp_wrap').offsetHeight - 45 // 45 is tab-header height+15
|
mainHeight.value = document.querySelector('.terminals_sftp_wrap').offsetHeight - 45 // 45 is tab-header height+15
|
||||||
})
|
})
|
||||||
@ -157,25 +224,33 @@ const handleSyncSession = () => {
|
|||||||
else $message.info('已关闭键盘输入到所有会话')
|
else $message.info('已关闭键盘输入到所有会话')
|
||||||
}
|
}
|
||||||
|
|
||||||
const terminalInput = ({ idx, key }) => {
|
const handleExecScript = (scriptObj) => {
|
||||||
if (!isSyncAllSession.value) return
|
// console.log(scriptObj.content)
|
||||||
let filterHostList = terminalTabRefs.value.filter((host, index) => {
|
if (!isSyncAllSession.value) return handleInputCommand(scriptObj.content)
|
||||||
return index !== idx
|
terminalRefs.value.forEach(terminalRef => {
|
||||||
|
terminalRef.inputCommand(scriptObj.content)
|
||||||
})
|
})
|
||||||
filterHostList.forEach(item => {
|
}
|
||||||
item.handleInputCommand(key)
|
|
||||||
|
const terminalInput = (command) => {
|
||||||
|
if (!isSyncAllSession.value) return
|
||||||
|
let filterTerminalRefs = terminalRefs.value.filter((host, index) => {
|
||||||
|
return index !== activeTabIndex.value
|
||||||
|
})
|
||||||
|
filterTerminalRefs.forEach(hostRef => {
|
||||||
|
hostRef.inputCommand(command)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabChange = async (index) => {
|
const tabChange = async (index) => {
|
||||||
await $nextTick()
|
await $nextTick()
|
||||||
const curTabTerminal = terminalTabRefs.value[index]
|
const curTerminalRef = terminalRefs.value[index]
|
||||||
curTabTerminal?.focusTab()
|
curTerminalRef?.focusTab()
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(terminalTabsLen, () => {
|
watch(terminalTabsLen, () => {
|
||||||
let len = terminalTabsLen.value
|
let len = terminalTabsLen.value
|
||||||
console.log('add tab:', len)
|
// console.log('add tab:', len)
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
activeTabIndex.value = len - 1
|
activeTabIndex.value = len - 1
|
||||||
// registryDbClick()
|
// registryDbClick()
|
||||||
@ -222,23 +297,23 @@ const handleFullScreen = () => {
|
|||||||
// removeTab(key)
|
// removeTab(key)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const handleVisibleSidebar = () => {
|
// const handleVisibleSidebar = () => {
|
||||||
visible.value = !visible.value
|
// visible.value = !visible.value
|
||||||
resizeTerminal()
|
// resizeTerminal()
|
||||||
}
|
// }
|
||||||
|
|
||||||
const resizeTerminal = () => {
|
const resizeTerminal = () => {
|
||||||
for (let terminalTabRef of terminalTabRefs.value) {
|
for (let terminalTabRef of terminalRefs.value) {
|
||||||
const { handleResize } = terminalTabRef || {}
|
const { handleResize } = terminalTabRef || {}
|
||||||
handleResize && handleResize()
|
handleResize && handleResize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleInputCommand = async (command) => {
|
const handleInputCommand = async (command) => {
|
||||||
const curTabTerminal = terminalTabRefs.value[activeTabIndex.value]
|
const curTerminalRef = terminalRefs.value[activeTabIndex.value]
|
||||||
await $nextTick()
|
await $nextTick()
|
||||||
curTabTerminal?.focusTab()
|
curTerminalRef?.focusTab()
|
||||||
curTabTerminal.handleInputCommand(`${ command }\n`)
|
curTerminalRef.inputCommand(`${ command }\n`)
|
||||||
showInputCommand.value = false
|
showInputCommand.value = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -265,34 +340,68 @@ const handleInputCommand = async (command) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$terminalTopHeight: 30px;
|
$terminalTopHeight: 30px;
|
||||||
|
|
||||||
.terminal_top {
|
.terminal_top {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: $terminalTopHeight;
|
height: $terminalTopHeight;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
border-bottom: 1px solid var(--el-color-primary);
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
background-color: #fff;
|
border-bottom: 1px solid #fff;
|
||||||
|
// background-color: #fff;
|
||||||
|
background: var(--el-fill-color-light);
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
:deep(.el-dropdown) {
|
user-select: none;
|
||||||
margin-top: -2px;
|
|
||||||
|
// :deep(.el-dropdown) {
|
||||||
|
// margin-top: -2px;
|
||||||
|
// }
|
||||||
|
.scripts_menu {
|
||||||
|
:deep(.el-dropdown-menu) {
|
||||||
|
min-width: 100px;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.link_text {
|
.link_text {
|
||||||
font-size: var(--el-font-size-base);
|
font-size: var(--el-font-size-base);
|
||||||
color: var(--el-color-primary);
|
color: var(--el-text-color-regular);
|
||||||
|
// color: var(--el-color-primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
|
|
||||||
|
.hidden_icon {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.full_icon {
|
|
||||||
cursor: pointer;
|
.left_menu {
|
||||||
margin-left: auto;
|
display: flex;
|
||||||
&:hover .icon {
|
align-items: center;
|
||||||
color: var(--el-color-primary);
|
}
|
||||||
|
|
||||||
|
.right_overview {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.switch_wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.full_icon {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover .icon {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.info_box {
|
.info_box {
|
||||||
height: calc(100% - $terminalTopHeight);
|
height: calc(100% - $terminalTopHeight);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user