✨ 新增凭证管理功能&字段储存
This commit is contained in:
parent
5c3818dd73
commit
5b2b776155
@ -1,30 +1,45 @@
|
|||||||
const { readHostList, writeHostList } = require('../utils')
|
const { readHostList, writeHostList, RSADecryptSync, AESEncryptSync, AESDecryptSync } = require('../utils')
|
||||||
|
|
||||||
async function getHostList({ res }) {
|
async function getHostList({ res }) {
|
||||||
// console.log('get-host-list')
|
// console.log('get-host-list')
|
||||||
const data = await readHostList()
|
let data = await readHostList()
|
||||||
data?.sort((a, b) => Number(b.index || 0) - Number(a.index || 0))
|
data?.sort((a, b) => Number(b.index || 0) - Number(a.index || 0))
|
||||||
|
data = data.map((item) => {
|
||||||
|
const isConfig = Boolean(item.username && item.port && (item[item.authType]))
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
isConfig,
|
||||||
|
password: '',
|
||||||
|
privateKey: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
res.success({ data })
|
res.success({ data })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveHost({
|
async function addHost({
|
||||||
res, request
|
res, request
|
||||||
}) {
|
}) {
|
||||||
let {
|
let {
|
||||||
body: {
|
body: {
|
||||||
name, host: newHost, index, expired, expiredNotify, group, consoleUrl, remark,
|
name, host: newHost, index, expired, expiredNotify, group, consoleUrl, remark,
|
||||||
port, username, authType, password, privateKey, command
|
port, username, authType, password, privateKey, credential, command, tempKey
|
||||||
}
|
}
|
||||||
} = request
|
} = request
|
||||||
// console.log(request)
|
// console.log(request)
|
||||||
if (!newHost || !name) return res.fail({ msg: 'missing params: name or host' })
|
if (!newHost || !name) return res.fail({ msg: 'missing params: name or host' })
|
||||||
let hostList = await readHostList()
|
let hostList = await readHostList()
|
||||||
if (hostList?.some(({ host }) => host === newHost)) return res.fail({ msg: `主机${ newHost }已存在` })
|
if (hostList?.some(({ host }) => host === newHost)) return res.fail({ msg: `主机${ newHost }已存在` })
|
||||||
if (!Array.isArray(hostList)) hostList = []
|
let record = {
|
||||||
hostList.push({
|
name, host: newHost, index, expired, expiredNotify, group, consoleUrl, remark,
|
||||||
host: newHost, name, index, expired, expiredNotify, group, consoleUrl, remark,
|
port, username, authType, password, privateKey, credential, command
|
||||||
port, username, authType, password, privateKey, command
|
}
|
||||||
})
|
const clearTempKey = await RSADecryptSync(tempKey)
|
||||||
|
console.log('clearTempKey:', clearTempKey)
|
||||||
|
const clearSSHKey = await AESDecryptSync(record[authType], clearTempKey)
|
||||||
|
// console.log(`${ authType }原密文: `, clearSSHKey)
|
||||||
|
record[authType] = await AESEncryptSync(clearSSHKey)
|
||||||
|
console.log(`${ authType }__commonKey加密存储: `, record[authType])
|
||||||
|
hostList.push(record)
|
||||||
await writeHostList(hostList)
|
await writeHostList(hostList)
|
||||||
res.success()
|
res.success()
|
||||||
}
|
}
|
||||||
@ -35,17 +50,31 @@ async function updateHost({
|
|||||||
let {
|
let {
|
||||||
body: {
|
body: {
|
||||||
host: newHost, name: newName, index, oldHost, expired, expiredNotify, group, consoleUrl, remark,
|
host: newHost, name: newName, index, oldHost, expired, expiredNotify, group, consoleUrl, remark,
|
||||||
port, username, authType, password, privateKey, command
|
port, username, authType, password, privateKey, credential, command, tempKey
|
||||||
}
|
}
|
||||||
} = request
|
} = request
|
||||||
if (!newHost || !newName || !oldHost) return res.fail({ msg: '参数错误' })
|
if (!newHost || !newName || !oldHost) return res.fail({ msg: '参数错误' })
|
||||||
let hostList = await readHostList()
|
let hostList = await readHostList()
|
||||||
if (!hostList.some(({ host }) => host === oldHost)) return res.fail({ msg: `原实例[${ oldHost }]不存在,请尝试新增实例` })
|
let record = {
|
||||||
let targetIdx = hostList.findIndex(({ host }) => host === oldHost)
|
|
||||||
hostList.splice(targetIdx, 1, {
|
|
||||||
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, command
|
port, username, authType, password, privateKey, credential, command
|
||||||
})
|
}
|
||||||
|
if (!hostList.some(({ host }) => host === oldHost)) return res.fail({ msg: `原实例[${ oldHost }]不存在,请尝试新增实例` })
|
||||||
|
|
||||||
|
let idx = hostList.findIndex(({ host }) => host === oldHost)
|
||||||
|
const oldRecord = hostList[idx]
|
||||||
|
// 如果存在原认证方式则保存下来
|
||||||
|
if (!record[authType] && oldRecord[authType]) {
|
||||||
|
record[authType] = oldRecord[authType]
|
||||||
|
} else {
|
||||||
|
const clearTempKey = await RSADecryptSync(tempKey)
|
||||||
|
console.log('clearTempKey:', clearTempKey)
|
||||||
|
const clearSSHKey = await AESDecryptSync(record[authType], clearTempKey)
|
||||||
|
// console.log(`${ authType }原密文: `, clearSSHKey)
|
||||||
|
record[authType] = await AESEncryptSync(clearSSHKey)
|
||||||
|
console.log(`${ authType }__commonKey加密存储: `, record[authType])
|
||||||
|
}
|
||||||
|
hostList.splice(idx, 1, record)
|
||||||
writeHostList(hostList)
|
writeHostList(hostList)
|
||||||
res.success()
|
res.success()
|
||||||
}
|
}
|
||||||
@ -89,7 +118,7 @@ async function removeHost({
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getHostList,
|
getHostList,
|
||||||
saveHost,
|
addHost,
|
||||||
updateHost,
|
updateHost,
|
||||||
removeHost
|
removeHost
|
||||||
// updateHostSort
|
// updateHostSort
|
||||||
|
@ -1,52 +1,81 @@
|
|||||||
const { readSSHRecord, writeSSHRecord, AESEncryptSync } = require('../utils')
|
const { readSSHRecord, writeSSHRecord, readHostList, writeHostList, RSADecryptSync, AESEncryptSync, AESDecryptSync } = require('../utils')
|
||||||
|
|
||||||
async function getSSHList({ res }) {
|
async function getSSHList({ res }) {
|
||||||
// console.log('get-host-list')
|
// console.log('get-host-list')
|
||||||
let data = await readSSHRecord()
|
let data = await readSSHRecord()
|
||||||
data = data?.map(item => {
|
data = data?.map(item => {
|
||||||
const { host, port, username, _id } = item
|
const { name, authType, _id: id, date } = item
|
||||||
return { host, port, username, _id }
|
return { id, name, authType, privateKey: '', password: '', date }
|
||||||
}) || []
|
}) || []
|
||||||
|
data.sort((a, b) => b.date - a.date)
|
||||||
res.success({ data })
|
res.success({ data })
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateSSH = async ({ res, request }) => {
|
const addSSH = async ({ res, request }) => {
|
||||||
let { body: { host, port, username, type, password, privateKey, randomKey, command } } = request
|
let { body: { name, authType, password, privateKey, tempKey } } = request
|
||||||
let record = { host, port, username, type, password, privateKey, randomKey, command }
|
let record = { name, authType, password, privateKey }
|
||||||
if(!host || !port || !username || !type || !randomKey) return res.fail({ data: false, msg: '参数错误' })
|
if(!name || !record[authType]) return res.fail({ data: false, msg: '参数错误' })
|
||||||
// 再做一次对称加密(方便ssh连接时解密)
|
|
||||||
record.randomKey = await AESEncryptSync(randomKey)
|
|
||||||
let sshRecord = await readSSHRecord()
|
let sshRecord = await readSSHRecord()
|
||||||
let idx = sshRecord.findIndex(item => item.host === host)
|
if (sshRecord.some(item => item.name === name)) return res.fail({ data: false, msg: '已存在同名凭证' })
|
||||||
if(idx === -1)
|
|
||||||
sshRecord.push(record)
|
const clearTempKey = await RSADecryptSync(tempKey)
|
||||||
else
|
console.log('clearTempKey:', clearTempKey)
|
||||||
sshRecord.splice(idx, 1, record)
|
const clearSSHKey = await AESDecryptSync(record[authType], clearTempKey)
|
||||||
|
// console.log(`${ authType }原密文: `, clearSSHKey)
|
||||||
|
record[authType] = await AESEncryptSync(clearSSHKey)
|
||||||
|
console.log(`${ authType }__commonKey加密存储: `, record[authType])
|
||||||
|
|
||||||
|
sshRecord.push({ ...record, date: Date.now() })
|
||||||
await writeSSHRecord(sshRecord)
|
await writeSSHRecord(sshRecord)
|
||||||
consola.info('新增凭证:', host)
|
consola.info('新增凭证:', name)
|
||||||
|
res.success({ data: '保存成功' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSSH = async ({ res, request }) => {
|
||||||
|
let { body: { id, name, authType, password, privateKey, date, tempKey } } = request
|
||||||
|
let record = { name, authType, password, privateKey, date }
|
||||||
|
if(!id || !name) return res.fail({ data: false, msg: '请输入凭据名称' })
|
||||||
|
let sshRecord = await readSSHRecord()
|
||||||
|
let idx = sshRecord.findIndex(item => item._id === id)
|
||||||
|
if (sshRecord.some(item => item.name === name && item.date !== date)) return res.fail({ data: false, msg: '已存在同名凭证' })
|
||||||
|
if(idx === -1) res.fail({ data: false, msg: '请输入凭据名称' })
|
||||||
|
const oldRecord = sshRecord[idx]
|
||||||
|
// 判断原记录是否存在当前更新记录的认证方式
|
||||||
|
if (!oldRecord[authType] && !record[authType]) return res.fail({ data: false, msg: `请输入${ authType === 'password' ? '密码' : '密钥' }` })
|
||||||
|
if (!record[authType] && oldRecord[authType]) {
|
||||||
|
record[authType] = oldRecord[authType]
|
||||||
|
} else {
|
||||||
|
const clearTempKey = await RSADecryptSync(tempKey)
|
||||||
|
console.log('clearTempKey:', clearTempKey)
|
||||||
|
const clearSSHKey = await AESDecryptSync(record[authType], clearTempKey)
|
||||||
|
// console.log(`${ authType }原密文: `, clearSSHKey)
|
||||||
|
record[authType] = await AESEncryptSync(clearSSHKey)
|
||||||
|
console.log(`${ authType }__commonKey加密存储: `, record[authType])
|
||||||
|
}
|
||||||
|
sshRecord.splice(idx, 1, record)
|
||||||
|
await writeSSHRecord(sshRecord)
|
||||||
|
consola.info('修改凭证:', name)
|
||||||
res.success({ data: '保存成功' })
|
res.success({ data: '保存成功' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeSSH = async ({ res, request }) => {
|
const removeSSH = async ({ res, request }) => {
|
||||||
let { body: { host } } = request
|
let { params: { id } } = request
|
||||||
let sshRecord = await readSSHRecord()
|
let sshRecord = await readSSHRecord()
|
||||||
let idx = sshRecord.findIndex(item => item.host === host)
|
let idx = sshRecord.findIndex(item => item._id === id)
|
||||||
if(idx === -1) return res.fail({ msg: '凭证不存在' })
|
if(idx === -1) return res.fail({ msg: '凭证不存在' })
|
||||||
sshRecord.splice(idx, 1)
|
sshRecord.splice(idx, 1)
|
||||||
consola.info('移除凭证:', host)
|
// 将删除的凭证id从host中删除
|
||||||
|
let hostList = await readHostList()
|
||||||
|
hostList = hostList.map(item => {
|
||||||
|
if (item.credential === id) item.credential = ''
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
await writeHostList(hostList)
|
||||||
|
consola.info('移除凭证:', id)
|
||||||
await writeSSHRecord(sshRecord)
|
await writeSSHRecord(sshRecord)
|
||||||
res.success({ data: '移除成功' })
|
res.success({ data: '移除成功' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const existSSH = async ({ res, request }) => {
|
|
||||||
let { body: { host } } = request
|
|
||||||
let sshRecord = await readSSHRecord()
|
|
||||||
let isExist = sshRecord?.some(item => item.host === host)
|
|
||||||
consola.info('查询凭证:', host)
|
|
||||||
if(!isExist) return res.success({ data: false }) // host不存在
|
|
||||||
res.success({ data: true }) // 存在
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCommand = async ({ res, request }) => {
|
const getCommand = async ({ res, request }) => {
|
||||||
let { host } = request.query
|
let { host } = request.query
|
||||||
if(!host) return res.fail({ data: false, msg: '参数错误' })
|
if(!host) return res.fail({ data: false, msg: '参数错误' })
|
||||||
@ -61,8 +90,8 @@ const getCommand = async ({ res, request }) => {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getSSHList,
|
getSSHList,
|
||||||
|
addSSH,
|
||||||
updateSSH,
|
updateSSH,
|
||||||
removeSSH,
|
removeSSH,
|
||||||
existSSH,
|
|
||||||
getCommand
|
getCommand
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const { getSSHList, updateSSH, removeSSH, existSSH, getCommand } = require('../controller/ssh')
|
const { getSSHList, addSSH, updateSSH, removeSSH, getCommand } = require('../controller/ssh')
|
||||||
const { getHostList, saveHost, updateHost, removeHost } = require('../controller/host')
|
const { getHostList, addHost, updateHost, removeHost } = require('../controller/host')
|
||||||
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')
|
||||||
@ -10,21 +10,21 @@ const ssh = [
|
|||||||
path: '/get-ssh-list',
|
path: '/get-ssh-list',
|
||||||
controller: getSSHList
|
controller: getSSHList
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
path: '/add-ssh',
|
||||||
|
controller: addSSH
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: 'post',
|
method: 'post',
|
||||||
path: '/update-ssh',
|
path: '/update-ssh',
|
||||||
controller: updateSSH
|
controller: updateSSH
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: 'post',
|
method: 'delete',
|
||||||
path: '/remove-ssh',
|
path: '/remove-ssh/:id',
|
||||||
controller: removeSSH
|
controller: removeSSH
|
||||||
},
|
},
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
path: '/exist-ssh',
|
|
||||||
controller: existSSH
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
method: 'get',
|
method: 'get',
|
||||||
path: '/command',
|
path: '/command',
|
||||||
@ -40,7 +40,7 @@ const host = [
|
|||||||
{
|
{
|
||||||
method: 'post',
|
method: 'post',
|
||||||
path: '/host-save',
|
path: '/host-save',
|
||||||
controller: saveHost
|
controller: addHost
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: 'put',
|
method: 'put',
|
||||||
|
@ -52,6 +52,7 @@ module.exports = (httpServer) => {
|
|||||||
const sshRecord = await readSSHRecord()
|
const sshRecord = await readSSHRecord()
|
||||||
let loginInfo = sshRecord.find(item => item.host === ip)
|
let loginInfo = sshRecord.find(item => item.host === ip)
|
||||||
if (!sshRecord.some(item => item.host === ip)) return socket.emit('create_fail', `未找到【${ ip }】凭证`)
|
if (!sshRecord.some(item => item.host === ip)) return socket.emit('create_fail', `未找到【${ ip }】凭证`)
|
||||||
|
// :TODO: 不用tempKey加密了,统一使用commonKey加密
|
||||||
let { type, host, port, username, randomKey } = loginInfo
|
let { type, host, port, username, randomKey } = loginInfo
|
||||||
try {
|
try {
|
||||||
// 解密放到try里面,防止报错【公私钥必须配对, 否则需要重新添加服务器密钥】
|
// 解密放到try里面,防止报错【公私钥必须配对, 否则需要重新添加服务器密钥】
|
||||||
|
@ -10,22 +10,25 @@ export default {
|
|||||||
getSSHList(params = {}) {
|
getSSHList(params = {}) {
|
||||||
return axios({ url: '/get-ssh-list', method: 'get', params })
|
return axios({ url: '/get-ssh-list', method: 'get', params })
|
||||||
},
|
},
|
||||||
|
addSSH(data) {
|
||||||
|
return axios({ url: '/add-ssh', method: 'post', data })
|
||||||
|
},
|
||||||
updateSSH(data) {
|
updateSSH(data) {
|
||||||
return axios({ url: '/update-ssh', method: 'post', data })
|
return axios({ url: '/update-ssh', method: 'post', data })
|
||||||
},
|
},
|
||||||
removeSSH(host) {
|
removeSSH(id) {
|
||||||
return axios({ url: '/remove-ssh', method: 'post', data: { host } })
|
return axios({ url: `/remove-ssh/${ id }`, method: 'delete' })
|
||||||
},
|
|
||||||
existSSH(host) {
|
|
||||||
return axios({ url: '/exist-ssh', method: 'post', data: { host } })
|
|
||||||
},
|
},
|
||||||
|
// existSSH(host) {
|
||||||
|
// return axios({ url: '/exist-ssh', method: 'post', data: { host } })
|
||||||
|
// },
|
||||||
getCommand(host) {
|
getCommand(host) {
|
||||||
return axios({ url: '/command', method: 'get', params: { host } })
|
return axios({ url: '/command', method: 'get', params: { host } })
|
||||||
},
|
},
|
||||||
getHostList() {
|
getHostList() {
|
||||||
return axios({ url: '/host-list', method: 'get' })
|
return axios({ url: '/host-list', method: 'get' })
|
||||||
},
|
},
|
||||||
saveHost(data) {
|
addHost(data) {
|
||||||
return axios({ url: '/host-save', method: 'post', data })
|
return axios({ url: '/host-save', method: 'post', data })
|
||||||
},
|
},
|
||||||
updateHost(data) {
|
updateHost(data) {
|
||||||
|
@ -52,11 +52,11 @@ let menuList = reactive([
|
|||||||
icon: markRaw(ScaleToOriginal),
|
icon: markRaw(ScaleToOriginal),
|
||||||
index: '/terminal'
|
index: '/terminal'
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// name: '凭据管理',
|
name: '凭据管理',
|
||||||
// icon: markRaw(Key),
|
icon: markRaw(Key),
|
||||||
// index: '/credentials'
|
index: '/credentials'
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
name: '分组管理',
|
name: '分组管理',
|
||||||
icon: markRaw(FolderOpened),
|
icon: markRaw(FolderOpened),
|
||||||
|
@ -49,6 +49,11 @@ const useStore = defineStore({
|
|||||||
// console.log('groupList:', groupList)
|
// console.log('groupList:', groupList)
|
||||||
this.$patch({ groupList })
|
this.$patch({ groupList })
|
||||||
},
|
},
|
||||||
|
async getSSHList() {
|
||||||
|
const { data: sshList } = await $api.getSSHList()
|
||||||
|
// console.log('sshList:', sshList)
|
||||||
|
this.$patch({ sshList })
|
||||||
|
},
|
||||||
getHostPing() {
|
getHostPing() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.hostList.forEach((item) => {
|
this.hostList.forEach((item) => {
|
||||||
|
@ -14,21 +14,11 @@ export const randomStr = (e) =>{
|
|||||||
// rsa公钥加密
|
// rsa公钥加密
|
||||||
export const RSAEncrypt = (text) => {
|
export const RSAEncrypt = (text) => {
|
||||||
const publicKey = localStorage.getItem('publicKey')
|
const publicKey = localStorage.getItem('publicKey')
|
||||||
if(!publicKey) return -1 // 公钥不存在
|
if (!publicKey) return -1 // 公钥不存在
|
||||||
const RSAPubEncrypt = new JSRsaEncrypt() // 生成实例
|
|
||||||
RSAPubEncrypt.setPublicKey(publicKey) // 配置公钥(不是将公钥实例化时传入!!!)
|
|
||||||
const ciphertext = RSAPubEncrypt.encrypt(text) // 加密
|
|
||||||
// console.log('rsa加密:', ciphertext)
|
|
||||||
return ciphertext
|
|
||||||
}
|
|
||||||
|
|
||||||
// rsa公钥解密
|
|
||||||
export const RSADecrypt = (text) => {
|
|
||||||
const publicKey = localStorage.getItem('publicKey')
|
|
||||||
if(!publicKey) return -1 // 公钥不存在
|
|
||||||
const RSAPubEncrypt = new JSRsaEncrypt() // 生成实例
|
const RSAPubEncrypt = new JSRsaEncrypt() // 生成实例
|
||||||
RSAPubEncrypt.setPublicKey(publicKey) // 配置公钥(不是将公钥实例化时传入!!!)
|
RSAPubEncrypt.setPublicKey(publicKey) // 配置公钥(不是将公钥实例化时传入!!!)
|
||||||
const ciphertext = RSAPubEncrypt.encrypt(text) // 加密
|
const ciphertext = RSAPubEncrypt.encrypt(text) // 加密
|
||||||
|
// console.log('rsa公钥加密:', ciphertext)
|
||||||
return ciphertext
|
return ciphertext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,204 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="">
|
<div class="credentials_container">
|
||||||
credentials
|
<div class="header">
|
||||||
|
<el-button type="primary" @click="addCredentials">添加凭证</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table v-loading="loading" :data="sshList">
|
||||||
|
<el-table-column prop="name" label="名称" />
|
||||||
|
<el-table-column prop="authType" label="类型">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.authType === 'privateKey' ? '密钥' : '密码' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" @click="handleChange(row)">修改</el-button>
|
||||||
|
<el-button v-show="row.id !== 'default'" type="danger" @click="removeSSH(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-dialog
|
||||||
|
v-model="sshFormVisible"
|
||||||
|
width="600px"
|
||||||
|
top="150px"
|
||||||
|
:title="isModify ? '修改凭证' : '添加凭证'"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="clearFormInfo"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="updateFormRef"
|
||||||
|
:model="sshForm"
|
||||||
|
: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="sshForm.name"
|
||||||
|
clearable
|
||||||
|
placeholder=""
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="认证方式" prop="type">
|
||||||
|
<el-radio v-model.trim="sshForm.authType" value="privateKey">密钥</el-radio>
|
||||||
|
<el-radio v-model.trim="sshForm.authType" value="password">密码</el-radio>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="sshForm.authType === 'privateKey'" prop="privateKey" label="密钥">
|
||||||
|
<el-button type="primary" size="small" @click="handleClickUploadBtn">
|
||||||
|
本地私钥...
|
||||||
|
</el-button>
|
||||||
|
<input
|
||||||
|
ref="privateKeyRef"
|
||||||
|
type="file"
|
||||||
|
name="privateKey"
|
||||||
|
style="display: none;"
|
||||||
|
@change="handleSelectPrivateKeyFile"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model.trim="sshForm.privateKey"
|
||||||
|
type="textarea"
|
||||||
|
:rows="5"
|
||||||
|
clearable
|
||||||
|
autocomplete="off"
|
||||||
|
style="margin-top: 5px;"
|
||||||
|
placeholder="-----BEGIN RSA PRIVATE KEY-----"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="sshForm.authType === 'password'" prop="password" label="密码">
|
||||||
|
<el-input
|
||||||
|
v-model.trim="sshForm.password"
|
||||||
|
type="text"
|
||||||
|
placeholder=""
|
||||||
|
autocomplete="off"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span>
|
||||||
|
<el-button @click="sshFormVisible = 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'
|
||||||
|
import { randomStr, AESEncrypt, RSAEncrypt } from '@utils/index.js'
|
||||||
|
|
||||||
|
const { proxy: { $api, $message, $messageBox, $store } } = getCurrentInstance()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const sshFormVisible = ref(false)
|
||||||
|
let isModify = ref(false)
|
||||||
|
const sshForm = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
data() {
|
authType: 'privateKey',
|
||||||
return {
|
privateKey: '',
|
||||||
}
|
password: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = computed(() => {
|
||||||
|
return {
|
||||||
|
name: { required: true, message: '需输入凭证名称', trigger: 'change' },
|
||||||
|
password: [{ required: !isModify.value && sshForm.authType === 'password', trigger: 'change' },],
|
||||||
|
privateKey: [{ required: !isModify.value && sshForm.authType === 'privateKey', trigger: 'change' },]
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateFormRef = ref(null)
|
||||||
|
const privateKeyRef = ref(null)
|
||||||
|
|
||||||
|
let sshList = computed(() => $store.sshList)
|
||||||
|
|
||||||
|
let addCredentials = () => {
|
||||||
|
sshForm.id = null
|
||||||
|
sshFormVisible.value = true
|
||||||
|
isModify.value = false
|
||||||
}
|
}
|
||||||
|
const handleChange = (row) => {
|
||||||
|
Object.assign(sshForm, { ...row })
|
||||||
|
sshFormVisible.value = true
|
||||||
|
isModify.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateForm = () => {
|
||||||
|
updateFormRef.value.validate()
|
||||||
|
.then(async () => {
|
||||||
|
let formData = { ...sshForm }
|
||||||
|
let tempKey = randomStr(16)
|
||||||
|
// 加密传输
|
||||||
|
if (formData.password) formData.password = AESEncrypt(formData.password, tempKey)
|
||||||
|
if (formData.privateKey) formData.privateKey = AESEncrypt(formData.privateKey, tempKey)
|
||||||
|
formData.tempKey = RSAEncrypt(tempKey)
|
||||||
|
// 加密传输
|
||||||
|
if (isModify.value) {
|
||||||
|
await $api.updateSSH(formData)
|
||||||
|
} else {
|
||||||
|
await $api.addSSH(formData)
|
||||||
|
}
|
||||||
|
sshFormVisible.value = false
|
||||||
|
await $store.getSSHList()
|
||||||
|
$message.success('success')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearFormInfo = () => {
|
||||||
|
nextTick(() => updateFormRef.value.resetFields())
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeSSH = ({ id, name }) => {
|
||||||
|
$messageBox.confirm(`确认删除该凭证:${ name }`, 'Warning', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
await $api.removeSSH(id) // 后台会同步删除关联此凭证的credential字段
|
||||||
|
await $store.getSSHList()
|
||||||
|
await $store.getHostList() // 刷新主机字段 isConfig
|
||||||
|
$message.success('success')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClickUploadBtn = () => {
|
||||||
|
privateKeyRef.value.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelectPrivateKeyFile = (event) => {
|
||||||
|
let file = event.target.files[0]
|
||||||
|
let reader = new FileReader()
|
||||||
|
reader.onload = (e) => {
|
||||||
|
sshForm.privateKey = e.target.result
|
||||||
|
privateKeyRef.value.value = ''
|
||||||
|
}
|
||||||
|
reader.readAsText(file)
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.credentials_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>
|
@ -46,6 +46,7 @@ onBeforeMount(async () => {
|
|||||||
.router_box {
|
.router_box {
|
||||||
min-height: calc(100vh - 60px - 20px);
|
min-height: calc(100vh - 60px - 20px);
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,6 @@ const { proxy: { $store, $api, $message, $router } } = getCurrentInstance()
|
|||||||
|
|
||||||
const loginFormRefs = ref(null)
|
const loginFormRefs = ref(null)
|
||||||
const isSession = ref(true)
|
const isSession = ref(true)
|
||||||
const visible = ref(true)
|
|
||||||
const notKey = ref(false)
|
const notKey = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const loginForm = reactive({
|
const loginForm = reactive({
|
||||||
|
@ -164,12 +164,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, getCurrentInstance } from 'vue'
|
import { computed, getCurrentInstance } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
import SSHForm from './ssh-form.vue'
|
const { proxy: { $api, $router, $tools } } = getCurrentInstance()
|
||||||
|
|
||||||
const { proxy: { $api, $tools } } = getCurrentInstance()
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
hostInfo: {
|
hostInfo: {
|
||||||
@ -226,22 +224,16 @@ const handleToConsole = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSSH = async () => {
|
const handleSSH = async () => {
|
||||||
let { data } = host.value
|
if(!hostInfo.value?.isConfig) {
|
||||||
}
|
|
||||||
|
|
||||||
const handleRemoveSSH = async () => {
|
|
||||||
ElMessageBox.confirm('确认删除SSH凭证', 'Warning', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(async () => {
|
|
||||||
let { data } = await $api.removeSSH(host.value)
|
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: data,
|
message: '请先配置SSH连接信息',
|
||||||
type: 'success',
|
type: 'warning',
|
||||||
center: true
|
center: true
|
||||||
})
|
})
|
||||||
})
|
handleUpdate()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$router.push({ path: '/terminal', query: { host: host.value } })
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRemoveHost = async () => {
|
const handleRemoveHost = async () => {
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="visible"
|
v-model="visible"
|
||||||
width="600px"
|
width="600px"
|
||||||
|
top="45px"
|
||||||
|
modal-class="host_form_dialog"
|
||||||
|
append-to-body
|
||||||
:title="title"
|
:title="title"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@open="setDefaultData"
|
@open="setDefaultData"
|
||||||
@ -46,8 +49,8 @@
|
|||||||
<div key="instance_info" class="instance_info">
|
<div key="instance_info" class="instance_info">
|
||||||
<el-form-item
|
<el-form-item
|
||||||
key="host"
|
key="host"
|
||||||
class="form_item"
|
class="form_item_host"
|
||||||
label="实例"
|
label="主机"
|
||||||
prop="host"
|
prop="host"
|
||||||
>
|
>
|
||||||
<el-input
|
<el-input
|
||||||
@ -59,7 +62,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item
|
||||||
key="port"
|
key="port"
|
||||||
class="form_item"
|
class="form_item_port"
|
||||||
label="端口"
|
label="端口"
|
||||||
prop="port"
|
prop="port"
|
||||||
>
|
>
|
||||||
@ -86,6 +89,7 @@
|
|||||||
<el-form-item key="authType" label="认证方式" prop="authType">
|
<el-form-item key="authType" label="认证方式" prop="authType">
|
||||||
<el-radio v-model.trim="hostForm.authType" value="privateKey">密钥</el-radio>
|
<el-radio v-model.trim="hostForm.authType" value="privateKey">密钥</el-radio>
|
||||||
<el-radio v-model.trim="hostForm.authType" value="password">密码</el-radio>
|
<el-radio v-model.trim="hostForm.authType" value="password">密码</el-radio>
|
||||||
|
<el-radio v-model.trim="hostForm.authType" value="credential">凭据</el-radio>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item
|
||||||
v-if="hostForm.authType === 'privateKey'"
|
v-if="hostForm.authType === 'privateKey'"
|
||||||
@ -131,6 +135,29 @@
|
|||||||
show-password
|
show-password
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="hostForm.authType === 'credential'"
|
||||||
|
key="credential"
|
||||||
|
prop="credential"
|
||||||
|
label="凭据"
|
||||||
|
>
|
||||||
|
<el-select v-model="hostForm.credential" class="credential_select" placeholder="">
|
||||||
|
<template #empty>
|
||||||
|
<div class="empty_credential">
|
||||||
|
<span>无凭据数据,</span>
|
||||||
|
<el-button type="primary" link @click="toCredentials">
|
||||||
|
去添加
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-option
|
||||||
|
v-for="item in sshList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item key="command" prop="command" label="执行指令">
|
<el-form-item key="command" prop="command" label="执行指令">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="hostForm.command"
|
v-model="hostForm.command"
|
||||||
@ -204,8 +231,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, getCurrentInstance, nextTick } from 'vue'
|
import { ref, reactive, computed, getCurrentInstance, nextTick } from 'vue'
|
||||||
|
import { RSAEncrypt, AESEncrypt, randomStr } from '@utils/index.js'
|
||||||
|
|
||||||
const { proxy: { $api, $message, $store } } = getCurrentInstance()
|
const { proxy: { $api, $router, $message, $store } } = getCurrentInstance()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: {
|
show: {
|
||||||
@ -229,6 +257,7 @@ const resetForm = () => ({
|
|||||||
authType: 'privateKey',
|
authType: 'privateKey',
|
||||||
password: '',
|
password: '',
|
||||||
privateKey: '',
|
privateKey: '',
|
||||||
|
credential: '', // credentials -> _id
|
||||||
index: 0,
|
index: 0,
|
||||||
expired: null,
|
expired: null,
|
||||||
expiredNotify: false,
|
expiredNotify: false,
|
||||||
@ -265,7 +294,8 @@ const visible = computed({
|
|||||||
|
|
||||||
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)
|
||||||
|
|
||||||
const handleClosed = () => {
|
const handleClosed = () => {
|
||||||
// console.log('handleClosed')
|
// console.log('handleClosed')
|
||||||
@ -312,14 +342,25 @@ const userSearch = (keyword, cb) => {
|
|||||||
cb(res)
|
cb(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toCredentials = () => {
|
||||||
|
visible.value = false
|
||||||
|
$router.push({ path: '/credentials' })
|
||||||
|
}
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
formRef.value.validate()
|
formRef.value.validate()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
let tempKey = randomStr(16)
|
||||||
|
let formData = { ...hostForm }
|
||||||
|
// 加密传输
|
||||||
|
if (formData.password) formData.password = AESEncrypt(formData.password, tempKey)
|
||||||
|
if (formData.privateKey) formData.privateKey = AESEncrypt(formData.privateKey, tempKey)
|
||||||
|
formData.tempKey = RSAEncrypt(tempKey)
|
||||||
if (props.defaultData) {
|
if (props.defaultData) {
|
||||||
let { msg } = await $api.updateHost(Object.assign({}, hostForm, { oldHost: oldHost.value }))
|
let { msg } = await $api.updateHost(Object.assign({}, formData, { oldHost: oldHost.value }))
|
||||||
$message({ type: 'success', center: true, message: msg })
|
$message({ type: 'success', center: true, message: msg })
|
||||||
} else {
|
} else {
|
||||||
let { msg } = await $api.saveHost(hostForm)
|
let { msg } = await $api.addHost(formData)
|
||||||
$message({ type: 'success', center: true, message: msg })
|
$message({ type: 'success', center: true, message: msg })
|
||||||
}
|
}
|
||||||
visible.value = false
|
visible.value = false
|
||||||
@ -332,9 +373,18 @@ const handleSave = () => {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.instance_info {
|
.instance_info {
|
||||||
display: flex;
|
display: flex;
|
||||||
.form_item {
|
justify-content: space-between;
|
||||||
width: 50%;
|
.form_item_host {
|
||||||
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
.form_item_port {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.empty_credential {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.dialog-footer {
|
.dialog-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1,205 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog
|
|
||||||
v-model="visible"
|
|
||||||
title="SSH连接"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
@closed="clearFormInfo"
|
|
||||||
>
|
|
||||||
<el-form
|
|
||||||
ref="formRef"
|
|
||||||
:model="sshForm"
|
|
||||||
:rules="rules"
|
|
||||||
:hide-required-asterisk="true"
|
|
||||||
label-suffix=":"
|
|
||||||
label-width="90px"
|
|
||||||
>
|
|
||||||
<el-form-item label="实例" prop="host">
|
|
||||||
<el-input
|
|
||||||
v-model.trim="sshForm.host"
|
|
||||||
disabled
|
|
||||||
clearable
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="端口" prop="port">
|
|
||||||
<el-input v-model.trim="sshForm.port" clearable autocomplete="off" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="用户名" prop="username">
|
|
||||||
<el-autocomplete
|
|
||||||
v-model.trim="sshForm.username"
|
|
||||||
:fetch-suggestions="userSearch"
|
|
||||||
style="width: 100%;"
|
|
||||||
clearable
|
|
||||||
>
|
|
||||||
<template #default="{item}">
|
|
||||||
<div class="value">{{ item.value }}</div>
|
|
||||||
</template>
|
|
||||||
</el-autocomplete>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="认证方式" prop="type">
|
|
||||||
<el-radio v-model.trim="sshForm.type" value="privateKey">密钥</el-radio>
|
|
||||||
<el-radio v-model.trim="sshForm.type" value="password">密码</el-radio>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item v-if="sshForm.type === 'password'" prop="password" label="密码">
|
|
||||||
<el-input
|
|
||||||
v-model.trim="sshForm.password"
|
|
||||||
type="password"
|
|
||||||
placeholder="Please input password"
|
|
||||||
autocomplete="off"
|
|
||||||
clearable
|
|
||||||
show-password
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item v-if="sshForm.type === 'privateKey'" prop="privateKey" label="密钥">
|
|
||||||
<el-button type="primary" size="small" @click="handleClickUploadBtn">
|
|
||||||
本地私钥...
|
|
||||||
</el-button>
|
|
||||||
<input
|
|
||||||
ref="privateKeyRef"
|
|
||||||
type="file"
|
|
||||||
name="privateKey"
|
|
||||||
style="display: none;"
|
|
||||||
@change="handleSelectPrivateKeyFile"
|
|
||||||
>
|
|
||||||
<el-input
|
|
||||||
v-model.trim="sshForm.privateKey"
|
|
||||||
type="textarea"
|
|
||||||
:rows="5"
|
|
||||||
clearable
|
|
||||||
autocomplete="off"
|
|
||||||
style="margin-top: 5px;"
|
|
||||||
placeholder="-----BEGIN RSA PRIVATE KEY-----"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="command" label="执行指令">
|
|
||||||
<el-input
|
|
||||||
v-model="sshForm.command"
|
|
||||||
type="textarea"
|
|
||||||
:rows="5"
|
|
||||||
clearable
|
|
||||||
autocomplete="off"
|
|
||||||
placeholder="连接服务器后自动执行的指令(例如: sudo -i)"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button @click="visible = false">取消</el-button>
|
|
||||||
<el-button type="primary" @click="handleSaveSSH">保存</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, reactive, computed, watch, getCurrentInstance, nextTick } from 'vue'
|
|
||||||
import { ElNotification } from 'element-plus'
|
|
||||||
import { randomStr, AESEncrypt, RSAEncrypt } from '@utils/index.js'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
show: {
|
|
||||||
required: true,
|
|
||||||
type: Boolean
|
|
||||||
},
|
|
||||||
tempHost: {
|
|
||||||
required: true,
|
|
||||||
type: String
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
required: true,
|
|
||||||
type: String
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:show',])
|
|
||||||
|
|
||||||
const formRef = ref(null)
|
|
||||||
const privateKeyRef = ref(null)
|
|
||||||
const sshForm = reactive({
|
|
||||||
host: '',
|
|
||||||
port: 22,
|
|
||||||
username: '',
|
|
||||||
type: 'privateKey',
|
|
||||||
password: '',
|
|
||||||
privateKey: '',
|
|
||||||
command: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const defaultUsers = [
|
|
||||||
{ value: 'root' },
|
|
||||||
{ value: 'ubuntu' },
|
|
||||||
]
|
|
||||||
|
|
||||||
const rules = reactive({
|
|
||||||
host: { required: true, message: '需输入主机', trigger: 'change' },
|
|
||||||
port: { required: true, message: '需输入端口', trigger: 'change' },
|
|
||||||
username: { required: true, message: '需输入用户名', trigger: 'change' },
|
|
||||||
type: { required: true },
|
|
||||||
password: { required: true, message: '需输入密码', trigger: 'change' },
|
|
||||||
privateKey: { required: true, message: '需输入密钥', trigger: 'change' },
|
|
||||||
command: { required: false }
|
|
||||||
})
|
|
||||||
|
|
||||||
const { proxy: { $api } } = getCurrentInstance()
|
|
||||||
|
|
||||||
const visible = computed({
|
|
||||||
get() {
|
|
||||||
return props.show
|
|
||||||
},
|
|
||||||
set(newVal) {
|
|
||||||
emit('update:show', newVal)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => props.tempHost, (newVal) => {
|
|
||||||
sshForm.host = newVal
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleClickUploadBtn = () => {
|
|
||||||
privateKeyRef.value.click()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSelectPrivateKeyFile = (event) => {
|
|
||||||
let file = event.target.files[0]
|
|
||||||
let reader = new FileReader()
|
|
||||||
reader.onload = (e) => {
|
|
||||||
sshForm.privateKey = e.target.result
|
|
||||||
privateKeyRef.value.value = ''
|
|
||||||
}
|
|
||||||
reader.readAsText(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSaveSSH = () => {
|
|
||||||
formRef.value.validate()
|
|
||||||
.then(async () => {
|
|
||||||
let randomKey = randomStr(16)
|
|
||||||
let formData = JSON.parse(JSON.stringify(sshForm))
|
|
||||||
// 加密传输
|
|
||||||
if (formData.password) formData.password = AESEncrypt(formData.password, randomKey)
|
|
||||||
if (formData.privateKey) formData.privateKey = AESEncrypt(formData.privateKey, randomKey)
|
|
||||||
formData.randomKey = RSAEncrypt(randomKey)
|
|
||||||
await $api.updateSSH(formData)
|
|
||||||
ElNotification({
|
|
||||||
title: '保存成功',
|
|
||||||
message: '下次点击 [Web SSH] 可直接登录终端\n如无法登录请 [移除凭证] 后重新添加',
|
|
||||||
type: 'success'
|
|
||||||
})
|
|
||||||
visible.value = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const userSearch = (keyword, cb) => {
|
|
||||||
let res = keyword
|
|
||||||
? defaultUsers.filter((item) => item.value.includes(keyword))
|
|
||||||
: defaultUsers
|
|
||||||
cb(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearFormInfo = () => {
|
|
||||||
nextTick(() => formRef.value.resetFields())
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -106,7 +106,7 @@ const unwatchHost = watch(hostList, () => {
|
|||||||
|
|
||||||
const connectIo = () => {
|
const connectIo = () => {
|
||||||
if (socket.value) socket.value.close()
|
if (socket.value) socket.value.close()
|
||||||
unwatchHost()
|
if (typeof(unwatchHost) === 'function') unwatchHost()
|
||||||
let socketInstance = io($serviceURI, {
|
let socketInstance = io($serviceURI, {
|
||||||
path: '/clients',
|
path: '/clients',
|
||||||
forceNew: true,
|
forceNew: true,
|
||||||
|
@ -1,239 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-form
|
|
||||||
ref="groupFormRef"
|
|
||||||
:model="groupForm"
|
|
||||||
:rules="rules"
|
|
||||||
:inline="true"
|
|
||||||
:hide-required-asterisk="true"
|
|
||||||
label-suffix=":"
|
|
||||||
>
|
|
||||||
<el-form-item label="" prop="name" style="width: 200px;">
|
|
||||||
<el-input
|
|
||||||
v-model.trim="groupForm.name"
|
|
||||||
clearable
|
|
||||||
placeholder="分组名称"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="addGroup"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="" prop="index" style="width: 200px;">
|
|
||||||
<el-input
|
|
||||||
v-model.number="groupForm.index"
|
|
||||||
clearable
|
|
||||||
placeholder="序号(数字, 用于分组排序)"
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="addGroup"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="">
|
|
||||||
<el-button type="primary" @click="addGroup">
|
|
||||||
添加
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<!-- 提示 -->
|
|
||||||
<el-alert type="success" :closable="false">
|
|
||||||
<template #title>
|
|
||||||
<span style="letter-spacing: 2px;">
|
|
||||||
Tips: 已添加实例数量 <u>{{ hostGroupInfo.total }}</u>
|
|
||||||
<span v-show="hostGroupInfo.notGroupCount">, 有 <u>{{ hostGroupInfo.notGroupCount }}</u> 台实例尚未分组</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-alert><br>
|
|
||||||
<el-alert type="success" :closable="false">
|
|
||||||
<template #title>
|
|
||||||
<span style="letter-spacing: 2px;"> Tips: 删除分组会将分组内所有实例移至默认分组 </span>
|
|
||||||
</template>
|
|
||||||
</el-alert>
|
|
||||||
<el-table v-loading="loading" :data="list">
|
|
||||||
<el-table-column prop="index" label="序号" />
|
|
||||||
<el-table-column prop="id" label="ID" />
|
|
||||||
<el-table-column prop="name" label="分组名称" />
|
|
||||||
<el-table-column label="关联实例数量">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-popover
|
|
||||||
v-if="row.hosts.list.length !== 0"
|
|
||||||
placement="right"
|
|
||||||
:width="350"
|
|
||||||
trigger="hover"
|
|
||||||
>
|
|
||||||
<template #reference>
|
|
||||||
<u class="host-count">{{ row.hosts.count }}</u>
|
|
||||||
</template>
|
|
||||||
<ul>
|
|
||||||
<li v-for="item in row.hosts.list" :key="item.host">
|
|
||||||
<span>{{ item.host }}</span>
|
|
||||||
-
|
|
||||||
<span>{{ item.name }}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</el-popover>
|
|
||||||
<u v-else class="host-count">0</u>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button type="primary" @click="handleChange(row)">修改</el-button>
|
|
||||||
<el-button v-show="row.id !== 'default'" type="danger" @click="deleteGroup(row)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<el-dialog
|
|
||||||
v-model="visible"
|
|
||||||
width="400px"
|
|
||||||
title="修改分组"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
>
|
|
||||||
<el-form
|
|
||||||
ref="updateFormRef"
|
|
||||||
:model="updateForm"
|
|
||||||
:rules="rules"
|
|
||||||
:hide-required-asterisk="true"
|
|
||||||
label-suffix=":"
|
|
||||||
label-width="100px"
|
|
||||||
>
|
|
||||||
<el-form-item label="分组名称" prop="name">
|
|
||||||
<el-input
|
|
||||||
v-model.trim="updateForm.name"
|
|
||||||
clearable
|
|
||||||
placeholder="分组名称"
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="分组序号" prop="index">
|
|
||||||
<el-input
|
|
||||||
v-model.number="updateForm.index"
|
|
||||||
clearable
|
|
||||||
placeholder="分组序号"
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button @click="visible = false">关闭</el-button>
|
|
||||||
<el-button type="primary" @click="updateGroup">修改</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, reactive, computed, onMounted, getCurrentInstance } from 'vue'
|
|
||||||
|
|
||||||
const { proxy: { $api, $message, $messageBox, $store } } = getCurrentInstance()
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const visible = ref(false)
|
|
||||||
const groupList = ref([])
|
|
||||||
const groupForm = reactive({
|
|
||||||
name: '',
|
|
||||||
index: ''
|
|
||||||
})
|
|
||||||
const updateForm = reactive({
|
|
||||||
name: '',
|
|
||||||
index: ''
|
|
||||||
})
|
|
||||||
const rules = reactive({
|
|
||||||
name: { required: true, message: '需输入分组名称', trigger: 'change' },
|
|
||||||
index: { required: true, type: 'number', message: '需输入数字', trigger: 'change' }
|
|
||||||
})
|
|
||||||
|
|
||||||
const groupFormRef = ref(null)
|
|
||||||
const updateFormRef = ref(null)
|
|
||||||
|
|
||||||
const hostGroupInfo = computed(() => {
|
|
||||||
const total = $store.hostList.length
|
|
||||||
const notGroupCount = $store.hostList.reduce((prev, next) => {
|
|
||||||
if (!next.group) prev++
|
|
||||||
return prev
|
|
||||||
}, 0)
|
|
||||||
return { total, notGroupCount }
|
|
||||||
})
|
|
||||||
|
|
||||||
const list = computed(() => {
|
|
||||||
return groupList.value.map(item => {
|
|
||||||
const hosts = $store.hostList.reduce((prev, next) => {
|
|
||||||
if (next.group === item.id) {
|
|
||||||
prev.count++
|
|
||||||
prev.list.push(next)
|
|
||||||
}
|
|
||||||
return prev
|
|
||||||
}, { count: 0, list: [] })
|
|
||||||
return { ...item, hosts }
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const getGroupList = () => {
|
|
||||||
loading.value = true
|
|
||||||
$api.getGroupList()
|
|
||||||
.then(({ data }) => {
|
|
||||||
groupList.value = data
|
|
||||||
groupForm.index = data.length
|
|
||||||
})
|
|
||||||
.finally(() => loading.value = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addGroup = () => {
|
|
||||||
groupFormRef.value.validate()
|
|
||||||
.then(() => {
|
|
||||||
const { name, index } = groupForm
|
|
||||||
$api.addGroup({ name, index })
|
|
||||||
.then(() => {
|
|
||||||
$message.success('success')
|
|
||||||
groupForm.name = ''
|
|
||||||
groupForm.index = ''
|
|
||||||
getGroupList()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = ({ id, name, index }) => {
|
|
||||||
updateForm.id = id
|
|
||||||
updateForm.name = name
|
|
||||||
updateForm.index = index
|
|
||||||
visible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateGroup = () => {
|
|
||||||
updateFormRef.value.validate()
|
|
||||||
.then(() => {
|
|
||||||
const { id, name, index } = updateForm
|
|
||||||
$api.updateGroup(id, { name, index })
|
|
||||||
.then(() => {
|
|
||||||
$message.success('success')
|
|
||||||
visible.value = false
|
|
||||||
getGroupList()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteGroup = ({ id, name }) => {
|
|
||||||
$messageBox.confirm(`确认删除分组:${ name }`, 'Warning', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
await $api.deleteGroup(id)
|
|
||||||
await $store.getHostList()
|
|
||||||
$message.success('success')
|
|
||||||
getGroupList()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getGroupList()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.host-count {
|
|
||||||
display: block;
|
|
||||||
width: 100px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 15px;
|
|
||||||
color: #87cf63;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,98 +0,0 @@
|
|||||||
<template>
|
|
||||||
<transition-group
|
|
||||||
name="list"
|
|
||||||
mode="out-in"
|
|
||||||
tag="ul"
|
|
||||||
class="host-list"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
v-for="(item, index) in list"
|
|
||||||
:key="item.host"
|
|
||||||
:draggable="true"
|
|
||||||
class="host-item"
|
|
||||||
@dragenter="dragenter($event, index)"
|
|
||||||
@dragover="dragover($event)"
|
|
||||||
@dragstart="dragstart(index)"
|
|
||||||
>
|
|
||||||
<span>{{ item.host }}</span>
|
|
||||||
---
|
|
||||||
<span>{{ item.name }}</span>
|
|
||||||
</li>
|
|
||||||
</transition-group>
|
|
||||||
<div style="display: flex; justify-content: center; margin-top: 25px">
|
|
||||||
<el-button type="primary" @click="handleUpdateSort">
|
|
||||||
保存
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, onMounted, getCurrentInstance } from 'vue'
|
|
||||||
|
|
||||||
const emit = defineEmits(['update-list',])
|
|
||||||
const { proxy: { $api, $message, $store } } = getCurrentInstance()
|
|
||||||
|
|
||||||
const targetIndex = ref(0)
|
|
||||||
const list = ref([])
|
|
||||||
|
|
||||||
const dragstart = (index) => {
|
|
||||||
targetIndex.value = index
|
|
||||||
}
|
|
||||||
|
|
||||||
const dragenter = (e, curIndex) => {
|
|
||||||
e.preventDefault()
|
|
||||||
if (targetIndex.value !== curIndex) {
|
|
||||||
let target = list.value.splice(targetIndex.value, 1)[0]
|
|
||||||
list.value.splice(curIndex, 0, target)
|
|
||||||
targetIndex.value = curIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const dragover = (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleUpdateSort = () => {
|
|
||||||
$api.updateHostSort({ list: list.value })
|
|
||||||
.then(({ msg }) => {
|
|
||||||
$message({ type: 'success', center: true, message: msg })
|
|
||||||
$store.sortHostList(list.value)
|
|
||||||
emit('update-list', list.value) // 触发自定义事件
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
list.value = $store.hostList.map(({ name, host }) => ({ name, host }))
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.drag-move {
|
|
||||||
transition: transform .3s;
|
|
||||||
}
|
|
||||||
.host-list {
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-right: 50px;
|
|
||||||
}
|
|
||||||
.host-item {
|
|
||||||
transition: all .3s;
|
|
||||||
box-shadow: var(--el-box-shadow-lighter);
|
|
||||||
cursor: move;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #595959;
|
|
||||||
padding: 0 20px;
|
|
||||||
margin: 0 auto;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #000;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
height: 35px;
|
|
||||||
line-height: 35px;
|
|
||||||
&:hover {
|
|
||||||
box-shadow: var(--el-box-shadow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dialog-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -2,7 +2,7 @@
|
|||||||
<div class="setting_container">
|
<div class="setting_container">
|
||||||
<el-tabs tab-position="top">
|
<el-tabs tab-position="top">
|
||||||
<el-tab-pane label="修改密码" lazy>
|
<el-tab-pane label="修改密码" lazy>
|
||||||
<Password />
|
<User />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<!-- <el-tab-pane label="分组管理">
|
<!-- <el-tab-pane label="分组管理">
|
||||||
<Group />
|
<Group />
|
||||||
@ -24,31 +24,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
|
||||||
import NotifyList from './components/notify-list.vue'
|
import NotifyList from './components/notify-list.vue'
|
||||||
import EmailList from './components/email-list.vue'
|
import EmailList from './components/email-list.vue'
|
||||||
// import Sort from './components/sort.vue'
|
|
||||||
import Record from './components/record.vue'
|
import Record from './components/record.vue'
|
||||||
// import Group from './components/group.vue'
|
import User from './components/user.vue'
|
||||||
import Password from './components/password.vue'
|
|
||||||
|
|
||||||
// const props = defineProps({
|
|
||||||
// show: {
|
|
||||||
// required: true,
|
|
||||||
// type: Boolean
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:show', 'update-list',])
|
|
||||||
|
|
||||||
// const visible = computed({
|
|
||||||
// get: () => props.show,
|
|
||||||
// set: (newVal) => emit('update:show', newVal)
|
|
||||||
// })
|
|
||||||
|
|
||||||
const emitUpdateList = () => {
|
|
||||||
emit('update-list')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onBeforeMount, getCurrentInstance } from 'vue'
|
import { ref, reactive, computed, onBeforeMount,defineProps, getCurrentInstance } from 'vue'
|
||||||
import TerminalTab from './terminal-tab.vue'
|
import TerminalTab from './terminal-tab.vue'
|
||||||
import InfoSide from './info-side.vue'
|
import InfoSide from './info-side.vue'
|
||||||
import SftpFooter from './sftp-footer.vue'
|
import SftpFooter from './sftp-footer.vue'
|
||||||
@ -59,6 +59,13 @@ import InputCommand from '@/components/input-command/index.vue'
|
|||||||
|
|
||||||
const { proxy: { $store, $router, $route, $nextTick } } = getCurrentInstance()
|
const { proxy: { $store, $router, $route, $nextTick } } = getCurrentInstance()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
ternimalTabs: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const name = ref('')
|
const name = ref('')
|
||||||
const host = ref('')
|
const host = ref('')
|
||||||
const activeTab = ref('')
|
const activeTab = ref('')
|
||||||
@ -71,6 +78,7 @@ const visible = ref(true)
|
|||||||
const infoSideRef = ref(null)
|
const infoSideRef = ref(null)
|
||||||
const terminalTabRefs = ref([])
|
const terminalTabRefs = ref([])
|
||||||
const token = computed(() => $store.token)
|
const token = computed(() => $store.token)
|
||||||
|
const ternimalTabs = computed(() => props.ternimalTabs)
|
||||||
|
|
||||||
const closable = computed(() => terminalTabs.length > 1)
|
const closable = computed(() => terminalTabs.length > 1)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div v-if="showLinkTips" class="terminal_link_tips">
|
<div v-if="showLinkTips" class="terminal_link_tips">
|
||||||
<h2 class="quick_link_text">快速连接</h2>
|
<h2 class="quick_link_text">快速连接</h2>
|
||||||
<el-table
|
<el-table
|
||||||
:data="tabelData"
|
:data="hostList"
|
||||||
:show-header="false"
|
:show-header="false"
|
||||||
>
|
>
|
||||||
<el-table-column prop="name" label="name" />
|
<el-table-column prop="name" label="name" />
|
||||||
@ -16,7 +16,7 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="actios_btns">
|
<div class="actios_btns">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="row.username && row.port"
|
v-if="row.isConfig"
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
@click="linkTerminal(row)"
|
@click="linkTerminal(row)"
|
||||||
@ -27,7 +27,7 @@
|
|||||||
v-else
|
v-else
|
||||||
type="success"
|
type="success"
|
||||||
link
|
link
|
||||||
@click="confSSH(row)"
|
@click="handleUpdateHost(row)"
|
||||||
>
|
>
|
||||||
配置ssh
|
配置ssh
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -37,46 +37,59 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<Terminal />
|
<Terminal :ternimal-tabs="ternimalTabs" />
|
||||||
</div>
|
</div>
|
||||||
|
<HostForm
|
||||||
|
v-model:show="hostFormVisible"
|
||||||
|
:default-data="updateHostData"
|
||||||
|
@update-list="handleUpdateList"
|
||||||
|
@closed="updateHostData = null"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, getCurrentInstance } from 'vue'
|
import { ref, computed, onActivated, getCurrentInstance } from 'vue'
|
||||||
import Terminal from './components/terminal.vue'
|
import Terminal from './components/terminal.vue'
|
||||||
|
import HostForm from '../server/components/host-form.vue'
|
||||||
|
|
||||||
const { proxy: { $store } } = getCurrentInstance()
|
const { proxy: { $store, $message } } = getCurrentInstance()
|
||||||
|
|
||||||
let showLinkTips = ref(true)
|
|
||||||
let ternimalTabs = ref([])
|
let ternimalTabs = ref([])
|
||||||
|
const hostFormVisible = ref(false)
|
||||||
|
const updateHostData = ref(null)
|
||||||
|
|
||||||
|
let showLinkTips = computed(() => !Boolean(ternimalTabs.value.length))
|
||||||
|
|
||||||
let hostList = computed(() => $store.hostList)
|
let hostList = computed(() => $store.hostList)
|
||||||
let sshList = computed(() => $store.sshList)
|
|
||||||
let tabelData = computed(() => {
|
|
||||||
return hostList.value.map(hostConf => {
|
|
||||||
// console.log(sshList.value)
|
|
||||||
let target = sshList.value?.find(sshConf => sshConf.host === hostConf.host)
|
|
||||||
if (target !== -1) {
|
|
||||||
return { ...hostConf, ...target }
|
|
||||||
}
|
|
||||||
return hostConf
|
|
||||||
})
|
|
||||||
})
|
|
||||||
let isAllConfssh = computed(() => {
|
let isAllConfssh = computed(() => {
|
||||||
return tabelData.value?.every(item => item.username && item.port)
|
return hostList.value?.every(item => item.isConfig)
|
||||||
})
|
})
|
||||||
|
|
||||||
function linkTerminal(row) {
|
function linkTerminal(row) {
|
||||||
// console.log(row)
|
// console.log(row)
|
||||||
ternimalTabs.value.push(row)
|
ternimalTabs.value.push(row)
|
||||||
showLinkTips.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function confSSH(row) {
|
function handleUpdateHost(row) {
|
||||||
|
hostFormVisible.value = true
|
||||||
|
updateHostData.value = { ...row }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleUpdateList = async () => {
|
||||||
|
try {
|
||||||
|
await $store.getHostList()
|
||||||
|
} catch (err) {
|
||||||
|
$message.error('获取实例列表失败')
|
||||||
|
console.error('获取实例列表失败: ', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
console.log()
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user