feat:外部打开Terminal (80%)

This commit is contained in:
zhangyu
2022-12-09 09:22:38 +08:00
parent 8a31717262
commit 132beefc1e
29 changed files with 2039 additions and 159 deletions

View File

@@ -950,15 +950,15 @@
}
@-webkit-keyframes backInRight {
0% {
-webkit-transform: translateX(2000px) scale(0.7);
transform: translateX(2000px) scale(0.7);
opacity: 0.7;
-webkit-transform: translateX(2000px);
transform: translateX(2000px);
opacity: 1;
}
80% {
-webkit-transform: translateX(0px) scale(0.7);
transform: translateX(0px) scale(0.7);
opacity: 0.7;
-webkit-transform: translateX(0px);
transform: translateX(0px);
opacity: 1;
}
100% {
@@ -969,15 +969,15 @@
}
@keyframes backInRight {
0% {
-webkit-transform: translateX(2000px) scale(0.7);
transform: translateX(2000px) scale(0.7);
opacity: 0.7;
-webkit-transform: translateX(2000px);
transform: translateX(2000px);
opacity: 1;
}
80% {
-webkit-transform: translateX(0px) scale(0.7);
transform: translateX(0px) scale(0.7);
opacity: 0.7;
-webkit-transform: translateX(0px);
transform: translateX(0px);
opacity: 1;
}
100% {
@@ -1081,8 +1081,8 @@
}
20% {
-webkit-transform: translateX(0px) scale(0.7);
transform: translateX(0px) scale(0.7);
-webkit-transform: translateX(0px);
transform: translateX(0px);
opacity: 0.7;
}
@@ -1100,14 +1100,14 @@
}
20% {
-webkit-transform: translateX(0px) scale(0.7);
transform: translateX(0px) scale(0.7);
-webkit-transform: translateX(0px);
transform: translateX(0px);
opacity: 0.7;
}
100% {
-webkit-transform: translateX(-2000px) scale(0.7);
transform: translateX(-2000px) scale(0.7);
-webkit-transform: translateX(-2000px);
transform: translateX(-2000px);
opacity: 0.7;
}
}
@@ -1123,15 +1123,13 @@
}
20% {
-webkit-transform: translateX(0px) scale(0.7);
transform: translateX(0px) scale(0.7);
opacity: 0.7;
-webkit-transform: translateX(0px);
transform: translateX(0px);
}
100% {
-webkit-transform: translateX(2000px) scale(0.7);
transform: translateX(2000px) scale(0.7);
opacity: 0.7;
-webkit-transform: translateX(2000px);
transform: translateX(2000px);
}
}
@keyframes backOutRight {
@@ -1142,15 +1140,13 @@
}
20% {
-webkit-transform: translateX(0px) scale(0.7);
transform: translateX(0px) scale(0.7);
opacity: 0.7;
-webkit-transform: translateX(0px);
transform: translateX(0px);
}
100% {
-webkit-transform: translateX(2000px) scale(0.7);
transform: translateX(2000px) scale(0.7);
opacity: 0.7;
-webkit-transform: translateX(2000px);
transform: translateX(2000px);
}
}
.animate__backOutRight {

View File

@@ -1,26 +1,62 @@
.fileDirectory {
height: 80%;
width: 45%;
min-width: 700px;
position: absolute;
bottom: 0;
background: #1E1E1E;
box-shadow: 5px 0 3px 0 #5E5E5E;
width: 100% !important;
top: -5px;
height: calc(100% + 30px);
right: -15px;
background: $--background-color-empty;
box-shadow: 5px 0 3px 0 $--explore-border-color-bottom;
z-index: 10;
font-size: 14px;
.file-directory-header{
display: flex;
font-size: 14px;
width: 100%;
justify-content: space-between;
padding: 0 10px;
background: #1E1E1E;
padding: 0 20px;
height: 36px;
line-height: 36px;
background: $--background-color-empty;
border-bottom: 1px solid rgba(51,51,51,0.10);
box-sizing: border-box;
color: #ffffff;
color:$--color-text-regular;
.header-option{
>i {
margin-right: 22px;
}
.nz-icon-close {
margin-right: 15px;
}
}
}
.file-directory-path{
display: flex;
width: 100%;
justify-content: space-between;
padding: 0 20px;
background: $--background-color-empty;
height: 36px;
line-height: 36px;
box-sizing: border-box;
color:$--color-text-regular;
.breadcrumb-box{
.nz-icon-edit{
margin-left: 15px;
display: none;
}
}
.breadcrumb-box:hover {
.nz-icon-edit{
display: inline-block;
}
}
.nz-icon-edit:hover{
color: $--color-primary;
cursor: pointer;
}
.breadcrumb-item{
color: #ffffff
color:$--color-text-regular
}
.breadcrumb-action{
cursor: pointer;
@@ -28,9 +64,17 @@
.breadcrumb-action:hover{
color: $--color-primary;
}
.path-option{
display: inline-block;
align-items: center;
.nz-icon-a-newfolder{
margin-left: 20px;
margin-right: 20px;
}
}
}
.file-directory-content{
height: calc(100% - 26px);
height: calc(100% - 60px);
width: calc(100% - 15px);
overflow: auto;
}
@@ -38,61 +82,74 @@
width: 6px;
}
.file-directory-content::-webkit-scrollbar-thumb {
background: rgba(244,244,244,0.16);
border-radius: 4px;
border:none
width: 6px;
}
.file-directory-content::-webkit-scrollbar-thumb:hover {
background: rgba(244,244,244,0.16);
border-radius: 4px;
border:none;
.file-feature{
display: none;
width: 6px;
}
.directory-content-header{
height: 32px;
line-height: 32px;
background: $--background-color-2;
width: calc(100% - 22px);
color: $--color-text-regular;
> div{text-transform:capitalize};
}
.file-name{
width: 44%;
box-sizing: border-box;
padding-left: 10px;
display: inline-block;
}
.file-size{
width: 15%;
display: inline-block;
}
.file-date{
width: 25%;
display: inline-block;
}
.file-opt{
width: 14%;
display: inline-block;
.nz-icon-shuxing{
margin-right: 20px;
}
}
.file-item{
font-family: Roboto-Regular;
font-size: 14px;
color: #B7B7B7;
line-height: 21px;
color: $--color-text-regular;
font-weight: 400;
margin-top: 8px;
}
.file-item{
display: flex;
padding: 0 10px;
.file-name{
width: calc(100% - 300px);
flex: 1;
}
.file-feature{
width: 100px;
flex-shrink: 1;
opacity: 0;
>.nz-icon-download1 {
margin-right: 24px;
}
}
.file-date {
width: 260px;
flex-shrink: 1;
display: flex;
justify-content: space-between;
flex-direction: row-reverse;
}
line-height: 24px;
height: 24px;
}
.file-item:hover{
background: rgba(255,134,0,0.50);
font-family: Roboto-Regular;
font-size: 14px;
color: #FF9230;
line-height: 21px;
font-weight: 400;
.file-feature{
opacity: 1;
}
}
.my-loading-box{
background: #1a1a1a;
}
.nz-icon:hover{
color: $--color-primary;
}
.file-info-item-header{
padding-bottom: 20px; border-bottom: 1px solid $--border-color-light;
}
.file-info-item{
display: flex;
margin-top: 10px;
align-items: center;
.file-info-item-left{
width: 180px;
flex-shrink: 1;
}
.file-info-item-right{
width: 200px;
flex-shrink: 1;
}
}
}

View File

@@ -3,46 +3,48 @@
z-index: 10;
right:0;
top:-100px;
max-height: 194px;
max-height: 246px;
min-width: 96px;
width: 308px;
background: #222329;
box-shadow: 1px 1px 4px -1px #1E1E1E;
background: $--background-color-empty;
box-shadow: -2px 1px 4px 0 rgba(0,0,0,0.06), 1px 1px 4px -1px rgba(0,0,0,0.16);
border-radius: 2px;
.file-state-panel-content::-webkit-scrollbar {
width: 6px;
}
.file-state-panel-content::-webkit-scrollbar-thumb {
background: rgba(244,244,244,0.16);
background: rgba(0,0,0,0.16);
border-radius: 4px;
border:none
}
.file-state-panel-content::-webkit-scrollbar-thumb:hover {
background: rgba(244,244,244,0.16);
background: rgba(0,0,0,0.16);
border-radius: 4px;
border:none
}
.file-state-panel-header{
height: 48px;
background: #222329;
box-shadow: 0 1px 0 0 #19191C;
background: $--background-color-empty;
box-shadow: 0 1px 0 0 $--background-color-disabled;
padding: 0 20px;
line-height: 48px;
display: flex;
justify-content: space-between;
text-transform: capitalize;
> i {
color: #ffffff;
color: $--color-text-primary;
}
}
.file-state-panel-title{
font-size: 14px;
color: #E7EAED;
letter-spacing: 0;
font-weight: 500;
text-transform: capitalize;
color: $--color-text-primary;
}
.file-state-panel-content {
min-height: 50px;
max-height: 146px;
max-height: 180px;
line-height: 48px;
padding-bottom: 15px;
border-radius: 0 0 2px 2px;
@@ -59,9 +61,9 @@
.item-icon{
width: 28px;
height: 28px;
background: #19191C;
background: $--background-color-base;
border-radius: 2px;
color: #fff;
color: $--color-text-regular;
display: flex;
justify-items: center;
justify-content: center;
@@ -76,21 +78,21 @@
flex-direction: column;
.item-progress-top{
width: 100%;
color: #fff;
color: $--color-text-regular;
height: 15px;
line-height: 15px;
font-size: 12px;
font-weight: 400;
margin-bottom: 3px;
margin-bottom: 4px;
}
.item-progress-middle{
}
.item-progress-bottom{
margin-top: 3px;
margin-top: 4px;
display: flex;
justify-content: space-between;
font-size: 10px;
color: #B7B7B7;
color: $--color-text-secondary;
line-height: 12px;
font-weight: 400;
}
@@ -99,7 +101,7 @@
flex-shrink:0;
width: 28px;
height: 28px;
color: #fff;
color: $--color-text-regular;
line-height: 28px;
text-align: center;
}
@@ -122,6 +124,6 @@
transform-origin: 70% 0%
}
.el-progress-bar__outer{
background-color: #19191C;
background-color: $--el-progress-bar__outer;
}
}

View File

@@ -253,6 +253,38 @@
.popper__arrow{
display: none;
}
.webshell-box-top{
border-bottom: 1px solid rgba(25,25,28,0.10);
}
.webshell-box-bottom{
border-top: 1px solid rgba(25,25,28,0.10);
}
.webshell-box-item{
height: 32px;
font-size: 14px;
color: $--color-text-primary;
letter-spacing: 0;
line-height: 32px;
.nz-icon {
margin-right: 8px;
}
}
.webshell-box-item:hover{
background: $--background-color-base;
color: $--color-primary;
}
}
.popover-webshell-new{
padding: 0 !important;
.webshell-box-top, .webshell-box-bottom{
height: 40px;
line-height: 40px;
box-sizing: border-box;
}
.webshell-box-top, .webshell-box-bottom,.webshell-box-item{
padding-left: 15px;
}
}
div.sp-header{
display: none;

View File

@@ -0,0 +1,147 @@
.web-terminal-new{
height: calc(100vh - 68px);
background: $--background-color-base;
/*border-top: 1px solid #BEBEBE;*/
box-shadow: 0 1px 0 0 $--border-color-light;
/deep/ .el-tabs{
border: none;
height: 100%;
width:100%;
margin-left:0px;
.el-tabs__nav-prev, .el-tabs__nav-next{
display: inline-block;
background: $--background-color-2 !important;
width: 20px;
height: 36px;
text-align: center;
line-height: 40px;
color: $--color-text-primary;
}
.el-tabs__nav-prev.is-disabled, .el-tabs__nav-next.is-disabled{
cursor: pointer !important;
display: inline-block;
background: $--background-color-2 !important;
width: 20px;
height: 36px;
text-align: center;
line-height: 40px;
color: $--color-text-primary;
}
.el-tabs__header{
height: 36px;
width: 100%;
.el-tabs__item{
height: 36px;
transform: translateY(-2px);
padding: 0 10px 0 10px;
margin-top: 0;
border-top: 2px solid transparent;
width: 126px;
background: $--background-color-empty;
line-height: 34px;
display: inline-block;
box-sizing: border-box;
.el-tabs__item-label{
display: inline-flex;
width: calc(100% - 20px);
align-items: center;
.active-icon{
flex-shrink: 1;
}
.el-tabs__item-label-name{
flex: 1;
width: calc(100% - 30px);
}
}
.el-icon-close{
display: none;
}
}
.el-tabs__item.is-active {
width: 200px;
border-top: 2px solid $--color-primary;
font-size: 14px;
color: $--color-text-primary;
}
.icon-reference{
display: none;
}
.el-tabs__item:hover{
width: 200px;
.icon-reference{
display: inline-block;
}
.el-icon-close{
display: inline-block;
}
}
.el-tabs__item:last-of-type {
height: 36px;
padding: 0 5px;
border: none;
border-top: 2px solid transparent;
background: transparent;
width: 40px;
line-height: 34px;
}
}
.el-tabs__content{
height: calc(100% - 36px);
background: #000;
box-sizing: border-box;
overflow: unset;
padding: 5px 5px;
.el-tab-pane{
height: 100%;
}
}
}
}
.webTerminal{
height: 100%;
width: 100%;
overflow: hidden;
.web-terminal-header{
height: 36px;
width: 100%;
display: flex;
justify-content: space-between;
box-sizing: border-box;
padding: 0 15px;
background: $--background-color-base;
box-shadow: inset 0 -1px 0 0 $--dropdown-menu-box-shadow-color;
font-size: 14px;
line-height: 36px;
color: $--color-text-primary;
img {
vertical-align: sub;
height: 20px;
width: 20px;
margin-right: 10px;
}
}
.right-tip {
position: absolute;
left: 8px;
top: 4px;
padding: 0 6px;
line-height: 15px;
height: 15px;
background-color: #ba3939;
opacity: .9;
border-radius: 7px;
color: white;
font-size: 6px;
}
.shell-input{
input {
background: #1E1E1E !important;
border: none;
}
input::input-placeholder{
color: #7C7C7C;
}
}
}

View File

@@ -4,6 +4,7 @@
@import './cli/webSSH.scss';
@import './cli/fileDirectory.scss';
@import './cli/fileListState.scss';
@import './cli/webSSHNew.scss';
@import './common/alert/alertLabel.scss';
@import './common/alert/alertStateInfo.scss';
@import './common/alert/alertRuleInfo.scss';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,97 @@
"css_prefix_text": "nz-icon-",
"description": "",
"glyphs": [
{
"icon_id": "680988",
"name": "属性",
"font_class": "shuxing",
"unicode": "e785",
"unicode_decimal": 59269
},
{
"icon_id": "33167519",
"name": "套接字文件",
"font_class": "taojieziwenjian",
"unicode": "e7bb",
"unicode_decimal": 59323
},
{
"icon_id": "33166559",
"name": "目录链接",
"font_class": "mululianjie",
"unicode": "e7b9",
"unicode_decimal": 59321
},
{
"icon_id": "33165863",
"name": "套接字文件链接",
"font_class": "taojieziwenjianlianjie",
"unicode": "e7ba",
"unicode_decimal": 59322
},
{
"icon_id": "33158668",
"name": "文件链接",
"font_class": "wenjianlianjie",
"unicode": "e7b5",
"unicode_decimal": 59317
},
{
"icon_id": "33158669",
"name": "管道文件链接",
"font_class": "guandaowenjianlianjie",
"unicode": "e7b6",
"unicode_decimal": 59318
},
{
"icon_id": "33158670",
"name": "块设备文件链接",
"font_class": "kuaishebeiwenjianlianjie",
"unicode": "e7b7",
"unicode_decimal": 59319
},
{
"icon_id": "33158671",
"name": "字符串设备文件链接",
"font_class": "zifuchuanshebeiwenjianlianjie",
"unicode": "e7b8",
"unicode_decimal": 59320
},
{
"icon_id": "33158636",
"name": "管道文件",
"font_class": "guandaowenjian",
"unicode": "e7b1",
"unicode_decimal": 59313
},
{
"icon_id": "33158637",
"name": "链接文件",
"font_class": "lianjiewenjian",
"unicode": "e7b2",
"unicode_decimal": 59314
},
{
"icon_id": "33158639",
"name": "块设备文件",
"font_class": "kuaishebeiwenjian",
"unicode": "e7b3",
"unicode_decimal": 59315
},
{
"icon_id": "33158641",
"name": "字符设备文件",
"font_class": "zifushebeiwenjian",
"unicode": "e7b4",
"unicode_decimal": 59316
},
{
"icon_id": "33112141",
"name": "home",
"font_class": "home",
"unicode": "e7af",
"unicode_decimal": 59311
},
{
"icon_id": "32964483",
"name": "File",

File diff suppressed because one or more lines are too long

View File

@@ -259,7 +259,8 @@ $--tooltip-background-color: #222329;
$--tooltip-border-color: rgba(112,116,122,0.6);
/* 17.label*/
$--label-background-color: $--background-color-empty;
/* 18 进度条颜色 */
$--el-progress-bar__outer: #19191C;
/*** themes/common.scss是与主题切换无关的变量 ***/
@import './src/common/var.scss';
@import './common.scss';

View File

@@ -254,7 +254,8 @@ $--tooltip-background-color: #ffffff;
$--tooltip-border-color: rgba(119,131,145,0.6);
/* 17.label*/
$--label-background-color: #D8d8d8;
/* 18 进度条颜色 */
$--el-progress-bar__outer: #ebeef5;
/*** themes/common.scss是与主题切换无关的变量 ***/
@import './src/common/var.scss';
@import './common.scss';

View File

@@ -699,7 +699,8 @@ const unitOptions = [
}
]
const units = []
window.onload = function () {
window.addEventListener('load', function () {
console.log(13213)
if (units.length < 1) {
unitOptions.forEach((item, index) => {
item.children.forEach((n, i) => {
@@ -707,8 +708,7 @@ window.onload = function () {
})
})
}
}
})
export default {
unitOptions: function () {
return unitOptions

View File

@@ -0,0 +1,323 @@
<style scoped>
.console{
height: 100%;
padding:5px 5px;
background-color: black;
position: relative;
}
</style>
<template>
<div :id="'ternimalContainer'+idIndex" class="console">
<div :id="'terminal'+idIndex" style="height: 100%"></div>
<fileDirectory :host="host" v-clickoutside="closeFileDir" :uuid="terminal.uuid" v-show="fileDirectoryShow" @close="showFileDir(false)" :fileDirectoryShow="fileDirectoryShow" ref="fileDirectory"/>
</div>
</template>
<script>
import Terminal from '../common/js/Xterm'
import fileDirectory from './fileDirectory'
export default {
name: 'console',
components: {
fileDirectory
},
props: {
terminal: { },
terminalType: { default: '1' },
idIndex: {
type: Number,
default: 0
},
isFullScreen: {
type: Boolean,
default: false
},
fontSize: {}
},
data () {
return {
term: null,
terminalSocket: null,
termimalRows: 15,
termimalHeight: 270,
topMenuHeight: 30,
dragHeigh: 8,
minRow: 1,
fontSpaceHeight: 2, // 一行高度=fontSize+fontSpaceHeight
obj: {
id: 2
},
isInit: false,
successBackContent: 'Connecting to',
failBackContent: 'Sorry',
connectFailContent: 'Connection failed',
welcomeBackContent: 'Welcome',
psdCont: 'password: ',
conFinish: false,
conSuccessNum: 0,
inputSecret: false,
userName: '',
fileDirectoryShow: false,
host: ''
}
},
watch: {
},
methods: {
prompt (term) {
term.write('\r\n ')// term.write('\r\n~$ ');
},
resize (consoleHeigt, consoleWidth) {
this.term.fit()
this.resizeServiceConsole()
},
resizeServiceConsole () {
const consoleBox = document.getElementById('ternimalContainer' + this.idIndex)
const width = document.body.clientWidth// 可视宽度
const height = parseInt(consoleBox.offsetHeight)
const winStyle = {
width: width,
height: height - 100,
cols: this.term.cols, // cols和rows在resizeConsole方法已经设置
rows: this.term.rows
}
this.$post('terminal/resize', winStyle).then(response => {
if (response.code === 200) {
this.term.fit()
} else {
this.$message.error(response.msg)
}
})
},
focusConsole () {
this.term.focus()
},
beforeCreate () {
let rows = this.termimalRows
// const consoleBox = document.getElementById('ternimalContainer' + this.idIndex)
const height = document.body.clientHeight// 高度
// consoleBox.style.height = `${height - 120}px`
rows = (height - this.topMenuHeight) / (this.fontSize + this.fontSpaceHeight)
rows = parseInt(rows)
const terminalContainer = document.getElementById('terminal' + this.idIndex)
this.term = new Terminal({
rows: rows, // 15行大概300px高无法设置heigh只能设置rows
cursorStyle: 'block', // 光标样式 null | 'block' | 'underline' | 'bar'
disableStdin: false, // 是否应禁用输入
fontSize: this.fontSize
})
this.term.open(terminalContainer)
this.term.focus()
const params = {
width: this.terminal.width,
height: this.terminal.height,
cols: this.terminal.cols,
rows: this.terminal.rows,
host: this.$loadsh.get(this.terminal, 'custom.host', ''),
port: this.$loadsh.get(this.terminal, 'custom.port', ''),
assetId: this.$loadsh.get(this.terminal, 'assetId', ''),
accountId: this.$loadsh.get(this.terminal, 'accountId', ''),
authProtocol: this.$loadsh.get(this.terminal, 'custom.authProtocol', ''),
authProtocolPort: this.$loadsh.get(this.terminal, 'custom.authProtocolPort', ''),
authType: this.$loadsh.get(this.terminal, 'custom.authType', ''),
authUsername: this.$loadsh.get(this.terminal, 'custom.authUsername', ''),
authPin: this.$loadsh.get(this.terminal, 'custom.authPin', ''),
authPriKey: this.$loadsh.get(this.terminal, 'custom.authPriKey', ''),
authUserTip: this.$loadsh.get(this.terminal, 'custom.authUserTip', ''),
authPinTip: this.$loadsh.get(this.terminal, 'custom.authPinTip', '')
}
this.$post('/terminal/login', params).then(res => {
console.log(res)
if (res.code == 200) {
this.terminal.uuid = res.data.uuid
this.$forceUpdate()
this.terminal.userName = res.data.authUsername + '@' + res.data.host
this.host = res.data.host
this.create()
} else {
this.terminal.uuid = res.data.uuid
this.terminal.userName = res.data.authUsername + '@' + res.data.host
this.host = res.data.host
params.name = this.terminal.name
this.$emit('loginFail', params)
this.$message.error(res.msg)
}
})
},
create () {
const that = this
const token = localStorage.getItem('nz-token')
let baseUrl = JSON.parse(JSON.stringify(this.$axios.defaults.baseURL))
const protocol = window.location.protocol.indexOf('https') > -1 ? 'wss' : 'ws'
if (baseUrl.startsWith('/')) {
baseUrl = `${protocol}://` + window.location.host + baseUrl
} else {
baseUrl = baseUrl.replace('http://', 'ws://').replace('https://', 'wss://')
}
let url = ''
this.terminal.height = document.body.clientHeight - 100
if (this.terminal.type === 'asset') {
url = baseUrl + '/terminal.ws?width=' + this.terminal.width + '&height=' + this.terminal.height + '&cols=' + this.terminal.cols + '&rows=' + this.terminal.rows + '&token=' + token + '&assetId=' + this.terminal.assetId + '&accountId=' + this.terminal.accountId + '&uuid=' + this.terminal.uuid
} else if (this.terminal.type === 'custom') {
url = baseUrl + '/terminal.ws?width=' + this.terminal.width + '&height=' + this.terminal.height + '&cols=' + this.terminal.cols + '&rows=' + this.terminal.rows + '&token=' + token + '&accountId=' + this.terminal.accountId + '&uuid=' + this.terminal.uuid
Object.keys(this.terminal.custom).forEach(key => {
if (this.terminal.custom[key]) {
url += '&' + key + '=' + this.terminal.custom[key]
}
})
}
this.terminalSocket = new WebSocket(url)
// 连接成功onclose
this.terminalSocket.onopen = () => {
this.terminal.isLogin = true
this.isInit = true
}
// 登录后,你输入的内容从后台服务返回
this.term.on('data', function (data) {
/*
let code = data.charCodeAt(0);
if(code==13){
}else {
//that.term.write(data);
} */
})
// 返回
this.terminalSocket.onmessage = function (evt) {
let backContent = evt.data
const welComIndex = backContent.indexOf(that.welcomeBackContent)
if (welComIndex > -1) { // 无服务器信息只与nezha进行了连接
const connectResult = {
title: '',
color: 1
}
that.$emit('refreshConsoleTitle', connectResult)// 1:grey 2 green 3 red
} else {
const successContentIndex = backContent.indexOf(that.successBackContent)
if (successContentIndex > -1) {
// that.conFinish = true;
const startIndex = successContentIndex + that.successBackContent.length + 1
backContent = backContent.substring(startIndex)
const endIndex = backContent.indexOf('\r\n')
const title = backContent.substring(0, endIndex)
const connectResult = {
title: title,
color: 2
}
that.$emit('refreshConsoleTitle', connectResult)// 1:grey 2 green 3 red
} else { // 失败
const failContentIndex = backContent.indexOf(that.failBackContent)
const connectFailIndex = backContent.indexOf(that.connectFailContent)
if (failContentIndex > -1) {
// that.conFinish = true;
const connectResult = {
title: '',
color: 3
}
that.$emit('refreshConsoleTitle', connectResult)// 1:grey 2 green 3 red
} else if (connectFailIndex > -1) {
const connectResult = {
title: '',
color: 3
}
that.$emit('refreshConsoleTitle', connectResult)// 1:grey 2 green 3 red
}
}
}
}
// 关闭
this.terminalSocket.onclose = () => {
this.terminal.isLogin = false
// 报错sorry的还没来得及看信息就关闭
// this.$emit("closeConsole",this.terminal.name);//
this.term && this.term.setOption('disableStdin', true)
}
// 错误
this.terminalSocket.onerror = (e) => {
this.terminal.isLogin = false
console.log(this.terminal.isLogin, 'close')
}
// 选中 复制
this.term.on('selection', function () {})
this.term.attachCustomKeyEventHandler(function (ev) { })
this.term.attach(this.terminalSocket)
this.term._initialized = true
this.term.fit()// 自适应大小(使终端的尺寸和几何尺寸适合于终端容器的尺寸) 只是width
this.$nextTick(() => { // 解决进入全屏和退出全屏是底部隐藏
this.setFontSize(this.fontSize)
})
},
closeSocket () {
if (this.terminalSocket) {
this.terminalSocket.close()
}
if (this.term) {
this.term.destroy()
}
// 初始化console的高度
this.conFinish = false
},
setFontSize (fontSize) {
this.term && this.term.setOption('fontSize', fontSize)
const consoleBox = document.getElementById('ternimalContainer' + this.idIndex)
const width = document.body.clientWidth// 可视宽度
console.log(consoleBox.offsetHeight)
let height = parseInt(consoleBox.offsetHeight)
if (height == null || !height) { height = this.termimalHeight }
const winStyle = {
width: width,
height: height,
cols: this.term.cols, // cols和rows在resizeConsole方法已经设置
rows: this.term.rows
}
// 调整终端可视区域高度
// document.getElementsByClassName('xterm-screen')[this.idIndex].style.height = height - 30 + 'px'
this.$nextTick(() => {
this.term.resize(this.term.cols, this.term.rows)
this.$post('terminal/resize', winStyle).then(response => {
if (response.code === 200) {
this.term.fit()
} else {
this.$message.error(response.msg)
}
})
})
},
reconnect () {
this.terminal.isLogin = false
this.closeSocket()
this.term.off('selection')
this.term.off('data')
this.beforeCreate()
},
closeFileDir () {
this.showFileDir(false)
},
showFileDir (show) {
if (JSON.stringify(show) == JSON.stringify(this.fileDirectoryShow)) {
return
}
if (show) {
this.fileDirectoryShow = show
}
let animationClass = ''
animationClass = show ? 'backInRight' : 'backOutRight'
this.animateCSS(this.$refs.fileDirectory.$el, animationClass).then((message) => {
this.fileDirectoryShow = show
})
}
},
mounted () {
this.beforeCreate()
},
beforeDestroy () {
this.closeSocket()
this.term.off('selection')
this.term.off('data')
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -1,35 +1,99 @@
<template>
<div class="fileDirectory" style="width: 100% !important;">
<div class="fileDirectory">
<div class="file-directory-header">
<span style="color: #fff">
{{$t('terminal.sftp')}}
<span style="color: #B7B7B7;margin-left: 10px">
<span @click="getSftpPath('/')" class="breadcrumb-item breadcrumb-action"><i class="nz-icon nz-icon-Computer"/></span>
<span v-for="(item,index) in breadcrumb" :key="index" class="breadcrumb-item">
/<span class="breadcrumb-action" @click="gotoPath(item,index)">{{item}}</span>
</span>
</span>
</span>
<span style="color: #fff" class="header-option">
<i class="nz-icon nz-icon-a-newfolder" @click="newFolderBoxShow = true"></i>
<span class="header-option">
<i class="nz-icon nz-icon-upload" @click="uploadFile"></i>
<i class="nz-icon nz-icon-close" @click="$emit('close')"></i>
</span>
</div>
<div class="file-directory-content" v-my-loading="fileDirectoryLoading">
<div v-if="fileDirectory !== '/'" @click="backFileDirectory" class="file-item"><i class="nz-icon nz-icon-a-upperlevel" style="margin-right: 10px"/>{{$t('terminal.back')}}</div>
<div v-for="(item,index) in fileList" :key="index" class="file-item" @click="selectFile(item)">
<div class="file-directory-path" v-clickoutside="hideEditPath">
<span v-show="!editPathShow" class="breadcrumb-box">
<span @click="getSftpPath('/')" class="breadcrumb-item breadcrumb-action"><i class="nz-icon nz-icon-home"/></span>
<span v-for="(item,index) in breadcrumb" :key="index" class="breadcrumb-item">
/<span class="breadcrumb-action" @click="gotoPath(item,index)">{{item}}</span>
</span>
<i class="nz-icon nz-icon-edit" @click="showEditPath"/>
</span>
<span v-show="editPathShow">
<el-input v-model="editPath" size="small" @keyup.enter.native="goEditPath"/>
</span>
<div class="path-option">
<span style="margin-right: 5px">Show hide File: </span>
<el-switch v-model="showHideFile"/>
<i class="nz-icon nz-icon-a-newfolder" @click="newFolderBoxShow = true"></i>
<i class="nz-icon nz-icon-upload" @click="uploadFile"></i>
</div>
</div>
<div style="padding: 0 20px;height: calc(100% - 50px)" v-my-loading="fileDirectoryLoading">
<div class="directory-content-header">
<div class="text-ellipsis file-name">
<i class="nz-icon" :class="selIcon(item)"/>
{{item.name}}
{{$t('overall.name')}}
</div>
<div class="file-feature" v-if="!item.isDir">
<i class="nz-icon nz-icon-download1" v-if="!item.isDir" @click="downloadFile(item)"></i>
<i class="nz-icon nz-icon-delete" v-if="!item.isDir" @click="delFile(item)"></i>
<div class="text-ellipsis file-size">
{{$t('backup.size')}}
</div>
<div class="file-date">
<span>{{momentTz(item.cts * 1000)}}</span>
<span v-if="!item.isDir">{{bytes(item.size, 0, 0)}}</span>
<div class="text-ellipsis file-date">
{{$t('issue.createTime')}}
</div>
<div class="text-ellipsis file-opt">
{{$t('overall.option')}}
</div>
</div>
<div class="file-directory-content">
<div v-if="fileDirectory !== '/'" @click="backFileDirectory" class="file-item">
<div class="text-ellipsis file-name">
<i class="nz-icon nz-icon-a-upperlevel" style="margin-right: 10px"/>
{{$t('terminal.back')}}
</div>
</div>
<div v-for="(item,index) in fileList" :key="index" class="file-item" @click="selectFile(item)">
<div class="text-ellipsis file-name">
<i class="nz-icon" :class="selIcon(item)"/>
{{item.name}}
</div>
<div class="text-ellipsis file-size">
<span v-if="!item.isDir">{{bytes(item.size, 0, 0)}}</span>
</div>
<div class="text-ellipsis file-date">
<span>{{momentTz(item.cts * 1000)}}</span>
</div>
<div class="text-ellipsis file-opt">
<i class="nz-icon nz-icon-shuxing" @click.stop="showFileInfo(item)"/>
<el-dropdown size="medium" trigger="click" @command="tableOperation">
<div class="table-operation-item table-operation-item--more" @click.stop="" :title="$t('overall.moreOperations')">
<i class="nz-icon nz-icon-more3"></i>
</div>
<el-dropdown-menu slot="dropdown" class="right-box-select-top right-public-box-dropdown-top">
<el-dropdown-item
:command="['rename', item]">
<i class="nz-icon nz-icon-edit"></i>
<span class="operation-dropdown-text">
{{$t('terminal.rename')}}
</span>
</el-dropdown-item>
<el-dropdown-item
:disabled="item.isDir"
:command="['download', item]">
<i class="nz-icon nz-icon-download1"></i>
<span class="operation-dropdown-text">
{{$t('overall.download')}}
</span>
</el-dropdown-item>
<el-dropdown-item
:disabled="item.isDir"
:command="['del', item]">
<i class="nz-icon nz-icon-delete"></i>
<span class="operation-dropdown-text">
{{$t('overall.delete')}}
</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- <i class="nz-icon nz-icon-download1" v-if="!item.isDir" @click="downloadFile(item)"></i>-->
<!-- <i class="nz-icon nz-icon-delete" v-if="!item.isDir" @click="delFile(item)"></i>-->
</div>
</div>
</div>
</div>
@@ -43,7 +107,7 @@
@close="newFolder(false)"
>
<div style="display: flex; align-items: center">
<div style="width: 100px;flex-shrink: 1">{{$t('overall.folderName')}}</div>
<div style="width: 100px;flex-shrink: 1;text-transform: capitalize">{{$t('overall.folderName')}}</div>
<el-input v-model="folder" size="small" style="flex: 1"/>
</div>
<div slot="footer">
@@ -57,6 +121,87 @@
</div>
</div>
</el-dialog>
<el-dialog
class="nz-dialog snapshot-dialog"
width="472px"
:title='$t("terminal.rename")'
destroy-on-close
:modal-append-to-body="false"
:visible.sync="renameBox"
@close="renameBox = false"
>
<div style="display: flex; align-items: center">
<div style="width: 100px;flex-shrink: 1;text-transform: capitalize">{{$t('overall.name')}}</div>
<el-input v-model="renameStr" size="small" style="flex: 1"/>
</div>
<div slot="footer">
<div class="el-message-box__btns">
<button class="nz-btn el-button el-button--small el-button--default" @click="renameBox = false">
<span>{{$t('overall.cancel')}}</span>
</button>
<button class="nz-btn el-button--small nz-btn-style-normal" @click="setRename">
<span style="text-transform:Capitalize">{{$t('overall.save')}}</span>
</button>
</div>
</div>
</el-dialog>
<el-dialog
class="nz-dialog snapshot-dialog"
width="472px"
:title='$t("overall.delete")'
destroy-on-close
:modal-append-to-body="false"
:visible.sync="delDialog"
@close="delDialog = false"
>
<div style="display: flex; align-items: center" v-if="delObj">
{{$t('terminal.delinfo',{fileName: delObj.name})}}
</div>
<div slot="footer">
<div class="el-message-box__btns">
<button class="nz-btn el-button el-button--small el-button--default" @click="delDialog = false" >
<span>{{$t('tip.no')}}</span>
</button>
<button class="nz-btn el-button--small nz-btn-style-normal" @click="delFile">
<span style="text-transform:Capitalize">{{$t('tip.yes')}}</span>
</button>
</div>
</div>
</el-dialog>
<!-- fileInfo-->
<el-dialog
class="nz-dialog snapshot-dialog"
width="472px"
:title='$t("overall.labels")'
destroy-on-close
:modal-append-to-body="false"
:visible.sync="fileInfoShow"
@close="fileInfoShow = false"
>
<div v-if="fileInfo">
<div class="file-info-item-header">
<i class="nz-icon" :class="selIcon(fileInfo)"/>
{{fileInfo.name}}
</div>
<div v-for="item in fileAttr" :key="item.name" class="file-info-item">
<div class="file-info-item-left" :class="{'is-disabled': fileInfo.isDir && item.key =='size'}">
{{$t(item.name)}} :
</div>
<div class="file-info-item-right">
{{selInfo(fileInfo, item.key)}}
</div>
</div>
</div>
<div slot="footer">
<div class="el-message-box__btns">
<button class="nz-btn el-button--small nz-btn-style-normal" @click="fileInfoShow = false">
<span style="text-transform:Capitalize">{{$t('overall.close')}}</span>
</button>
</div>
</div>
</el-dialog>
</div>
</template>
@@ -66,7 +211,8 @@ export default {
name: 'fileDirectory',
props: {
uuid: {},
fileDirectoryShow: {}
fileDirectoryShow: {},
host: {}
},
data () {
return {
@@ -76,7 +222,27 @@ export default {
newFolderBoxShow: false,
folder: '',
fileDirectoryLoading: false,
timer: ''
timer: '',
editPathShow: false,
editPath: '',
showHideFile: false,
delObj: '',
delDialog: false,
renameBox: false,
renameStr: '',
fileInfo: '',
fileInfoShow: false,
fileAttr: [
{ name: 'overall.type', key: 'isDir' },
{ name: 'config.operationlog.ip', key: 'ip' },
{ name: 'asset.location', key: 'location' },
{ name: 'backup.size', key: 'size' },
{ name: 'terminal.modifyTime', key: 'uts' },
{ name: 'Owner', key: 'Owner' },
{ name: 'dashboard.panel.chartForm.group', key: 'group' },
{ name: 'config.menus.perms', key: 'permissionsString' }
]
}
},
computed: {
@@ -113,12 +279,33 @@ export default {
init () {
this.getSftpPath(this.fileDirectory)
},
showEditPath () {
this.editPath = this.fileDirectory
this.editPathShow = true
},
hideEditPath () {
this.editPath = ''
this.editPathShow = false
},
goEditPath () {
console.log('123123123')
this.getSftpPath(this.editPath)
setTimeout(() => {
this.editPath = ''
this.editPathShow = false
})
},
selectFile (item) {
if (item.isDir) {
const path = this.fileDirectory == '/' ? '' : this.fileDirectory
this.getSftpPath(path + '/' + item.name)
}
},
showFileInfo (item) {
console.log(item)
this.fileInfo = item
this.fileInfoShow = true
},
newFolder (flag) {
if (!flag) {
this.newFolderBoxShow = false
@@ -146,16 +333,23 @@ export default {
this.getSftpPath(path || '/')
},
getSftpPath (path) {
this.breadcrumb = path.split('/').filter(item => item)
const params = {
uuid: this.uuid,
path: path
path: path,
showHideFile: this.showHideFile
}
this.fileDirectoryLoading = true
this.$post('/terminal/sftp/ls', params).then(res => {
this.fileDirectoryLoading = false
this.fileDirectory = res.data.path
this.fileList = res.data.list
if (res.code === 200) {
this.fileDirectoryLoading = false
this.fileDirectory = res.data.path
this.fileList = res.data.list
this.editPath = ''
this.breadcrumb = res.data.path.split('/').filter(item => item)
} else {
this.$message.error(res.msg)
this.editPath = ''
}
})
},
uploadFile () {
@@ -166,6 +360,7 @@ export default {
type: 'upload',
isStart: false,
isFinish: false,
isError: false,
importFileList: [],
total: '',
speed: '',
@@ -174,7 +369,8 @@ export default {
cancel: '',
axios: '',
timer: '',
done: 0
done: 0,
delObj: ''
}
this.$store.dispatch('uploadFile', params)
},
@@ -192,6 +388,7 @@ export default {
type: 'download',
isStart: false,
isFinish: false,
isError: false,
total: '',
speed: '',
fileLength: '',
@@ -202,7 +399,11 @@ export default {
this.$store.dispatch('dispatchAddFileList', params)
}, 300)
},
delFile (item) {
setRename () {
this.renameBox = false
},
delFile () {
const item = this.delObj
const path = this.fileDirectory == '/' ? '' : this.fileDirectory
const params = {
uuid: this.uuid,
@@ -210,8 +411,10 @@ export default {
}
this.$post('/terminal/sftp/rm', params).then(res => {
if (res.code == 200) {
this.delDialog = false
this.getSftpPath(this.fileDirectory)
} else {
this.delDialog = false
this.getSftpPath(this.fileDirectory)
this.$message.error(res.msg)
}
@@ -227,6 +430,66 @@ export default {
return 'nz-icon-File'
}
return 'nz-icon-File'
},
selInfo (item, key) {
if (key === 'isDir') {
if (item.isDir) {
return this.$t('terminal.catalogueFile')
} else {
return this.$t('backup.File')
}
}
if (key === 'ip') {
return this.host
}
// fileAttr: [
// { name: 'overall.type', key: 'isDir' },
// { name: 'config.operationlog.ip', key: 'ip' },
// { name: 'asset.location', key: 'location' },
// { name: 'backup.size', key: 'size' },
// { name: 'terminal.modifyTime', key: 'uts' },
// { name: 'Owner', key: 'Owner' },
// { name: 'dashboard.panel.chartForm.group', key: 'group' },
// { name: 'config.menus.perms', key: 'permissionsString' }
//
// ]
if (key === 'location') {
return this.fileDirectory
}
if (key === 'size' && !item.isDir) {
return this.bytes(item.size, 0, 0)
} else if (key === 'size' && item.isDir) {
return ''
}
if (key === 'uts') {
return this.momentTz(item.uts * 1000)
}
if (key === 'Owner') {
return 'Owner'
}
if (key === 'group') {
return 'group'
}
if (key === 'permissionsString') {
return item.permissionsString
}
return '-'
},
tableOperation ([command, row, param]) {
switch (command) {
case 'rename':
this.renameStr = row.name
this.renameBox = true
break
case 'download':
this.downloadFile(row)
break
case 'del':
this.delObj = row
this.delDialog = true
// this.delFile(row)
break
}
}
}
}

View File

@@ -12,11 +12,13 @@
<div class="item-progress">
<div class="item-progress-top text-ellipsis">{{item.name}}</div>
<div class="item-progress-middle">
<el-progress :show-text="false" :percentage="item.done" :color="customColorMethod"></el-progress>
<el-progress :show-text="false" :percentage="item.done" :color="customColorMethod.bind(item)"></el-progress>
</div>
<div class="item-progress-bottom">
<span>{{bytes(item.total, 0, 0)}}</span>
<span>{{item.speed}}</span>
<span v-if="!item.isFinish && !item.isError">{{item.speed}}</span>
<span v-if="item.isFinish">Completed</span>
<span v-if="item.isError">Error</span>
</div>
</div>
<div class="item-state">
@@ -107,7 +109,8 @@ export default {
},
methods: {
bytes: chartDataFormat.getUnit(7).compute,
customColorMethod (percentage) {
customColorMethod (percentage, item) {
console.log(item)
if (percentage < 100) {
return '#3B92F1'
} else {

View File

@@ -0,0 +1,94 @@
<template>
<div class="webTerminal">
<div class="web-terminal-header">
<div> <img alt="loading..." height="26" :src="logo?logo:require('../../assets/img/logo1-2.png')"/>Web terminal</div>
<div>
<div class="console-title-icon" style='right: 106px;display: inline;position: absolute' @click="showFileState" v-show="fileList.length">
<i class="nz-icon nz-icon-a-filetransfer" :title="$t('terminal.filetransfer')"></i>
<span v-show="fileList.length>0" class="right-tip">{{fileList.length<=99?fileList.length:'99+'}}</span>
</div>
{{name}}</div>
</div>
<fileListState v-clickoutside="hideFileState" ref="fileListState"/>
<webSSHNew ref="websshNew" />
<el-input :placeholder="'发送文本到所有SSH终端'" size="small" class="shell-input" v-model="message"/>
</div>
</template>
<script>
import webSSHNew from '@/components/cli/webSSHNew'
import fileListState from './fileListState'
export default {
name: 'terminal',
components: {
webSSHNew,
fileListState
},
data () {
return {
logo: '',
fileListStateType: '',
message: '',
name: ''
}
},
computed: {
language () { return this.$store.getters.getLanguage },
fileList () {
return this.$store.getters.getFileList
}
},
created () {
const self = this
window.addEventListener('setItemEvent', function (e) {
if (e.key == 'nz-sys-logo' && e.value) {
self.logo = e.value
}
})
this.logo = localStorage.getItem('nz-sys-logo')
},
mounted () {
const self = this
this.name = localStorage.getItem('nz-username')
window.onbeforeunload = () => {
const opener = window.opener
opener.postMessage(
JSON.stringify({
close: true
})
)
}
window.addEventListener('message', function (e) {
console.log(e)
if (e.data) {
try {
const data = JSON.parse(e.data)
self.$get('asset/asset/' + data.id).then(res => {
const asset = res.data
self.$refs.websshNew.addConsole(asset.id, asset.manageIp, '', '', 'asset')
})
} catch (e) {
console.log(e)
}
}
})
},
methods: {
showFileState () {
let type = 'down'
this.fileListStateType = type = 'down'
this.$refs.fileListState.fileStateShow(!this.$refs.fileListState.fileStateBox, type)
},
hideFileState () {
this.$refs.fileListState.fileStateShow(false, this.fileListStateType)
}
},
beforeDestroy () {
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -31,12 +31,10 @@
:popper-class="'popover-webshell'"
>
<div>
Session ID : {{item.uuid.slice(0,7).toLocaleUpperCase()}}
Session ID : {{item.terminal.uuid.slice(0,7).toLocaleUpperCase()}}
</div>
<span slot="reference"> <i class="nz-icon nz-icon-about" /></span>
</el-popover>
<div :class="{'active-icon grey':item.circleColor == 1,'active-icon green':item.circleColor == 2,'active-icon red':item.circleColor == 3}"
style="margin-top: 0px;"></div>{{item.title}}
</span>
<my-console

View File

@@ -0,0 +1,680 @@
<template>
<div class="ani-webSHH-height web-terminal-new" id="web-terminal-new">
<el-tabs :before-leave="beforeLeave"
@tab-click="handleClick"
@tab-remove="removeTab"
type="border-card"
v-model="editableTabsValue" >
<el-tab-pane :key="item.name"
:label="item.title"
:name="item.name"
closable
v-for="(item, index) in editableTabs"
>
<!-- tab显示的内容 1 grey,2 green, 3 red-->
<span slot="label" class="el-tabs__item-label">
<div class="active-icon" :class="item.terminal.isLogin ? 'green-bg': 'red-bg'"></div>
<div class="el-tabs__item-label-name text-ellipsis">
{{item.terminal.userName}}
</div>
<el-popover
slot="label"
placement="bottom"
width="180"
trigger="click"
:popper-class="'popover-webshell popover-webshell-new'"
>
<div class="popover-webshell-box">
<div class="webshell-box-top">
Session ID : {{item.terminal.uuid.slice(0,7).toLocaleUpperCase()}}
</div>
<div class="webshell-box-middle" v-show="item.terminal.userName">
<div @click="duplicate(item, index)" class="webshell-box-item">
<i class="nz-icon nz-icon-override"/>
{{$t('overall.duplicate')}}
</div>
<div @click="reconnect(item, index)" v-show="item.terminal.userName" class="webshell-box-item">
<i class="nz-icon nz-icon-reconnect"></i>
{{$t('terminal.reconnect')}}
</div>
<div @click="showFileDir(item, index)" class="webshell-box-item" v-if="item.terminal.isLogin && item.terminal.terminalType == '1'">
<i class="nz-icon nz-icon-SFTP"></i>
{{$t('terminal.sftp')}}
</div>
</div>
<div class="webshell-box-item webshell-box-bottom" @click="closeShell(item,index)">
<div >
<i class="nz-icon nz-icon-close"/>
{{$t('overall.close')}}
</div>
</div>
</div>
<span slot="reference" class="icon-reference"> <i class="nz-icon nz-icon-more1" /></span>
</el-popover>
</span>
<terminal
:fontSize="fontSize"
:terminalType="item.terminal.terminalType"
:idIndex="index"
:ref="'console'+index"
:terminal="item.terminal"
@loginFail="loginFail"
@closeConsole="removeTab"
@refreshConsoleTitle="refreshTabTitle"
></terminal>
</el-tab-pane>
<el-tab-pane key="add" name="addConsole" class="add-console" style="width: 20px">
<el-popover
slot="label"
placement="bottom-start"
width="150"
trigger="hover"
:popper-class="'popover-webshell'"
>
<div>
<div class="popover-webshell-item" @click="assetShowChange"><i class="nz-icon nz-icon-menu-assets" />{{$t('webshell.selAsset')}}</div>
<div class="popover-webshell-item" @click="customShow=true"><i class="nz-icon nz-icon-edit" />{{$t('webshell.custom')}}</div>
</div>
<span slot="reference" style="padding:8px;font-size:20px;font-weight:bold;">+</span>
</el-popover>
</el-tab-pane>
</el-tabs>
<!--弹窗-->
<el-dialog :modal-append-to-body='false' :show-close="true" :visible.sync="assetShow" @close="closeAssetCustom" class="nz-dialog" width="620px">
<div slot="title">{{$t('webshell.connect')}}</div>
<div >
<el-form label-width="120px" size="small" :model="assetContent" label-position = "top" :rules="rules" ref="assetConnect" v-my-loading="assetLoading" >
<el-form-item :label='$t("overall.asset")' prop="assetId" class="flex">
<el-dropdown trigger="click" class="header-el-dropdown">
<span class="el-dropdown-link">
<span>{{selectValue}}</span>
<span><i class="el-icon-arrow-down el-icon--right"></i></span>
</span>
<el-dropdown-menu style="width: 118px" class="el-dropdown__width right-box-select-top right-public-box-dropdown-top" placement="bottom-end" slot="dropdown">
<el-dropdown-item
@click.native="selectAssetAuth(item.label, item.value)"
v-for="item in searchMetrics"
:key="item.value"><i class="nz-icon" :class="item.icon" style="margin-right: 5px"/>{{item.label}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<v-selectpage
ref="selectPage"
style="flex: 1"
data="asset/asset"
:params="selectPageParams"
:tb-columns="columns"
key-field="id"
show-field="manageIp"
search-field="manageIp"
v-model="assetContent.assetId"
size="small"
:language="language"
:placeholder="$t('dashboard.panel.chartForm.selectAsset')"
id="box-input-asset-id"
:result-format="resultFormat"></v-selectpage>
<button :disabled="prevent_opt.save" class="nz-btn nz-btn-size-normal nz-btn-style-normal" type="button" @click.prevent="connect">Connect</button>
</el-form-item>
</el-form>
</div>
</el-dialog>
<el-dialog :modal-append-to-body='false' :show-close="true" :visible.sync="customShow" @close="closeAssetCustom" class="nz-dialog" width="620px"destroy-on-close >
<div slot="title">{{$t('webshell.connect')}}</div>
<div >
<el-form label-width="120px" size="small" :model="customConnect" label-position = "top" :rules=" customConnect.authProtocol ===2 ? rulesCustom2: rulesCustom" ref="customConnect" v-my-loading="assetLoading" class="custom">
<el-form-item :label='$t("webshell.protocol")' prop="authProtocol">
<el-select @change="protocolChange" value-key="id" popper-class="config-dropdown w260 right-box-select-top right-public-box-dropdown-top" v-model="customConnect.authProtocol" placeholder="" size="small" id="webshell-box-input-protocol">
<el-option v-for="item in authProtocol" :id="'dc-principal-op-'+item.value" :key="item.value" :label="item.name" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item :label='$t("asset.authType")' prop="authType" @change="authTypeChange" >
<el-select value-key="id" popper-class="config-dropdown w260 right-box-select-top right-public-box-dropdown-top" v-model="customConnect.authType" :disabled="customConnect.authProtocol === 2" placeholder="" size="small" id="webshell-box-input-protocol">
<el-option v-for="item in authType" :id="'dc-principal-op-'+item.value" :key="item.value" :label="item.name" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item :label='$t("asset.host")' prop="host">
<el-input v-model="customConnect.host" size="small"/>
</el-form-item>
<el-form-item :label='$t("asset.port")' prop="port">
<el-input v-model="customConnect.port" size="small"/>
</el-form-item>
<el-form-item :label='$t("profile.username")' prop="authUsername">
<el-input v-model="customConnect.authUsername" size="small" autocomplete="new-password"/>
</el-form-item>
<el-form-item
v-if="customConnect.authType === 2"
:label='$t("asset.talon.token")'
prop="authPriKey"
>
<el-input v-model="customConnect.authPriKey" size="small" autocomplete="new-password"/>
</el-form-item>
<el-form-item :label='$t("login.pin")' prop="authPin"
:rules="[
{ required: customConnect.authType ===1, message:$t('validate.required'), trigger: 'change'},
]">
<el-input v-model="customConnect.authPin" size="small" type="password" autocomplete="new-password"/>
</el-form-item>
<el-form-item
v-if="customConnect.authProtocol === 2"
:label='$t("asset.usernamePrompt")'
prop="authUserTip">
<el-input v-model="customConnect.authUserTip" size="small"/>
</el-form-item>
<el-form-item
v-if="customConnect.authProtocol === 2"
:label='$t("asset.pinPrompt")'
prop="authPinTip"
>
<el-input v-model="customConnect.authPinTip" size="small"/>
</el-form-item>
<div class="right-box__footer custom-footer">
<button id="asset-edit-cancel" @click="customShow=false" class="footer__btn footer__btn--light" type="button">
<span>{{$t('overall.cancel')}}</span>
</button>
<button id="asset-edit-save" :disabled="prevent_opt.save" class="footer__btn" @click.prevent="connect" type="button">
<span>{{$t('webshell.connect')}}</span>
</button>
</div>
</el-form>
</div>
</el-dialog>
</div>
</template>
<script>
import terminal from './consoleNew'
import { host, port } from '@/components/common/js/validate'
export default {
name: 'webSSH',
components: {
terminal
},
computed: {
language () { return this.$store.getters.getLanguage },
fileList () {
return this.$store.getters.getFileList
}
},
data () {
return {
tabIndex: 0,
editableTabs: [],
editableTabsValue: '',
fontSize: 16,
assetShow: false,
selectValue: 'SSH',
fileListStateType: 'down',
searchMetrics: [
{
value: 'SSH',
label: 'SSH'
},
{
value: 'Telnet',
label: 'Telnet'
}
],
selectPageParams: {
authProtocol: 1
},
authProtocol: [
{
value: 1,
name: 'SSH'
},
{
value: 2,
name: 'TELNET'
}
],
authType: [
{
value: 1,
name: 'Password'
},
{
value: 2,
name: 'Key'
}
],
consoleShow: false,
closeConfirmShow: false,
customConnect: {
host: '',
port: 22,
authType: 1,
authUsername: '',
authPin: '',
authPriKey: '',
authUserTip: '',
authPinTip: '',
authProtocolPort: '',
authProtocol: 1
},
columns: [
{ title: 'ID', data: 'id' },
{
title: 'Name',
data: function (row) {
if (row.name.length > 15) {
return row.name.substring(0, 12) + '...'
}
return row.name
}
},
{ title: 'Manage Ip', data: 'manageIp' },
{
title: 'Type',
data: (row) => {
return row.type ? row.type.name : ''
}
},
{
title: 'Model',
data: (row) => {
return row.model ? row.model.name : ''
}
},
{
title: 'Datacenter',
data: (row) => {
return row.dc ? row.dc.name : ''
}
}
],
rules: {
assetId: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' }
]
},
rulesCustom: {
authProtocol: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' }
],
authType: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' }
],
host: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' },
{ validator: host, trigger: 'change' }
],
port: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' },
{ validator: port, trigger: 'change' }
],
authUsername: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' }
],
authPin: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' }
]
// authUserTip: [
// { validator: this.authUserTipValid, trigger: 'change' }
// ],
// authPinTip: [
// { validator: this.authPinTipValid, trigger: 'change' }
// ],
// authPriKey: [
// { validator: this.authPriKeyValid, trigger: 'change' }
// ]
},
rulesCustom2: {
authProtocol: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' }
],
authType: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' }
],
host: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' },
{ validator: host, trigger: 'change' }
],
port: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' },
{ validator: port, trigger: 'change' }
],
authUsername: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' }
],
authPin: [
{ required: false, message: this.$t('validate.required'), trigger: 'change' }
],
authUserTip: [
{ required: false, message: this.$t('validate.required'), trigger: 'change' }
],
authPinTip: [
{ required: false, message: this.$t('validate.required'), trigger: 'change' }
],
authPriKey: [
{ required: true, message: this.$t('validate.required'), trigger: 'change' }
]
},
assetData: [],
assetLoading: false,
assetContent: {
assetId: ''
},
customShow: false
}
},
methods: {
selectAssetAuth (val, label) {
this.$refs.selectPage.remove()
if (val) {
this.selectValue = val
if (val === 'SSH') {
this.selectPageParams = { authProtocol: 1, pageNumber: 1 }
} else {
this.selectPageParams = { authProtocol: 2, pageNumber: 1 }
}
} else {
label = 'SSH'
this.selectPageParams = { authProtocol: 1, pageNumber: 1 }
}
setTimeout(() => {
this.$refs.selectPage.pageChange()
}, 100)
},
assetShowChange () {
this.assetShow = true
this.selectValue = 'SSH'
this.selectPageParams = { authProtocol: 1, pageNumber: 1 }
if (this.$refs.selectPage) {
this.$refs.selectPage.remove()
this.$refs.selectPage.pageChange()
}
// this.getAssetData()
},
/* 活动标签切换时触发 */
beforeLeave (currentName, oldName) {
// 重点如果name是add则什么都不触发
if (currentName === 'add') {
this.addTab()
return false
} else {
// 切换tab
this.$nextTick(() => {
if (this.editableTabs && this.editableTabs.length > 0) {
this.editableTabs.forEach((tab, index) => {
if (tab.name === currentName) {
this.$refs['console' + index][0].focusConsole()
this.currentUuid = tab.terminal.uuid
}
})
}
})
this.currentIndex = currentName
}
},
handleClick () {
},
windowChange () { // 窗口大小改变
// alert('winChange');
if (this.editableTabs && this.editableTabs.length > 0) {
this.editableTabs.forEach((tab, index) => {
this.$refs['console' + index][0].resize()
})
}
},
removeTab (targetName) {
const tabs = this.editableTabs
let activeName = this.editableTabsValue
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
const nextTab = tabs[index + 1] || tabs[index - 1]
if (nextTab) {
activeName = nextTab.name
}
}
})
}
this.editableTabsValue = activeName
this.editableTabs = tabs.filter(tab => tab.name !== targetName)
if (this.editableTabs.length <= 0) {
// this.closeConsole()
}
},
loginFail (params) {
this.removeTab(params.name)
setTimeout(() => {
if (params.assetId) {
this.assetShowChange()
} else {
this.customConnect = {
...params,
authType: Number(params.authType),
authPin: atob(decodeURIComponent(params.authPin))
}
this.customShow = true
}
})
},
refreshTabTitle (connectResult) {
if (this.editableTabs && this.editableTabs.length > 0) {
this.editableTabs.forEach((tab, index) => {
if (tab.name === this.editableTabsValue) {
if (connectResult.title && connectResult.title != '') {
tab.title = connectResult.title
}
tab.circleColor = connectResult.color
}
})
}
},
cancleConfirm () {
this.closeConfirmShow = false
},
resultFormat (resp) {
if (resp && resp.data) {
const assetData = {}
assetData.list = resp.data.list
assetData.totalRow = resp.data.total
return assetData
}
},
getAssetData () {
this.assetLoading = true
this.$get('asset/asset', { pageSize: -1, typeIds: '1,2' }).then(res => {
this.assetLoading = false
this.assetData = res.data.list
})
},
connect () {
this.prevent_opt.save = true
if (this.assetShow) {
this.$refs.assetConnect.validate((valid) => {
if (valid) {
this.$get('asset/asset/' + this.assetContent.assetId).then(res => {
const asset = res.data
this.addConsole(asset.id, asset.manageIp, '', '', 'asset')
this.assetShow = false
this.prevent_opt.save = false
})
} else {
this.prevent_opt.save = false
}
})
} else {
this.$refs.customConnect.validate((valid) => {
if (valid) {
this.addConsole('', this.customConnect.host, '', this.customConnect.port, 'custom')
this.customShow = false
this.prevent_opt.save = false
} else {
this.prevent_opt.save = false
}
})
}
},
protocolChange () {
if (this.customConnect.authProtocol === 1) {
this.customConnect.authUserTip = ''
this.customConnect.authPinTip = ''
this.customConnect.port = 22
} else {
this.customConnect.authPriKey = ''
this.customConnect.port = 23
this.customConnect.authType = 1
}
setTimeout(() => {
this.$refs.customConnect.clearValidate()
})
},
authTypeChange () {
if (this.customConnect.authType === 1) {
this.customConnect.authPriKey = ''
}
},
encode (str) {
// 对编码的字符串转化base64
const base64 = encodeURIComponent(btoa(str))
return base64
},
closeAssetCustom () {
this.assetShow = false
this.customShow = false
this.assetContent.assetId = ''
this.customConnect = {
host: '',
port: 22,
authType: 1,
authUsername: '',
authPin: '',
authPriKey: '',
authUserTip: '',
authPinTip: '',
authProtocolPort: '',
authProtocol: 1
}
},
addConsole (id, host, accountId, port, type) {
if (!id) { id = '' }
if (!host) { host = '' }
if (!accountId) { accountId = '' }
if (!port) { port = '' }
const uuid = ''
const newTabName = ++this.tabIndex + ''
let title = host
if (port) {
title = title + ':' + port
}
if (!title) {
title = this.$t('webshell.shellTitle')
}
const width = document.body.clientWidth// 可视宽度
const console = {
title: title,
name: newTabName,
circleColor: 1, // 1 grey,2 green, 3 red
uuid: uuid,
terminal: {
name: newTabName,
cols: 225,
rows: 200,
width: width,
height: this.consoleHeight,
assetId: id,
accountId: accountId,
uuid: uuid,
type: type,
username: '',
isLogin: false
}
}
if (type === 'custom') {
console.terminal.custom = {
host: this.customConnect.host,
port: this.customConnect.port,
authType: this.customConnect.authType,
terminalType: this.customConnect.authProtocol,
authUsername: encodeURIComponent(this.customConnect.authUsername),
authPin: this.encode(this.customConnect.authPin),
authPriKey: encodeURIComponent(this.customConnect.authPriKey),
authUserTip: encodeURIComponent(this.customConnect.authUserTip),
authPinTip: encodeURIComponent(this.customConnect.authPinTip),
authProtocolPort: encodeURIComponent(this.customConnect.authProtocolPort),
authProtocol: encodeURIComponent(this.customConnect.authProtocol)
}
}
if (id) {
this.$get('/asset/asset/' + id).then(res => {
console.terminal.terminalType = res.data.type.authProtocol
console.terminal.username = ''
this.editableTabsValue = newTabName
this.editableTabs.push(console)
})
} else {
console.terminal.username = ''
this.editableTabsValue = newTabName
this.editableTabs.push(console)
}
},
duplicate (item, index) {
console.log(item, index)
if (item.terminal.assetId) {
const newTabName = ++this.tabIndex + ''
this.editableTabsValue = newTabName
this.editableTabs.push(console)
} else {
this.customConnect = {
...item.terminal.custom,
authType: Number(item.terminal.custom.authType),
authPin: atob(decodeURIComponent(item.terminal.custom.authPin))
}
this.customShow = true
}
},
debounce (operate, delay) {
let time = null
let timer = null
let newTime = null
function task () {
newTime = +new Date()
if (newTime - time < delay) {
timer = setTimeout(task, delay)
} else {
operate()
timer = null
}
time = newTime
}
return function () {
// 更新时间戳
time = +new Date()
if (!timer) {
timer = setTimeout(task, delay)
}
}
},
reconnect (item, index) {
console.log(item, index)
this.$refs['console' + index][0].reconnect()
},
showFileDir (item, index) {
this.$refs['console' + index][0].showFileDir(true)
},
closeShell (item, index) {
this.removeTab(item.name)
}
},
watch: {
},
created () {
},
mounted () {
window.addEventListener('resize', this.debounce(this.windowChange, 1000), false)
},
beforeDestroy () {
window.removeEventListener('resize', this.debounce(this.windowChange, 1000), false)
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -142,7 +142,12 @@ export default {
methods: {
...mapActions(['logoutSuccess']),
cli () {
this.$store.commit('openConsole')
// this.$store.commit('openConsole')
if (!this.externalTerminal) {
this.$store.dispatch('dispatchExternalTerminal')
} else {
this.$store.dispatch('dispatchOpenExternalTerminalWindow')
}
},
// 打开全局搜索
openGlobalSearch () {
@@ -283,6 +288,9 @@ export default {
},
isShrink () {
return this.$store.getters.getIsShrink
},
externalTerminal () {
return this.$store.getters.getExternalTerminal
}
},
beforeDestroy () {

View File

@@ -1,8 +1,8 @@
<template>
<div class="home" :class="mode" v-my-loading.dark="loading">
<left-menu @refresh="refresh"></left-menu>
<div class="home" :class="mode" v-my-loading.dark="loading" >
<left-menu @refresh="refresh" v-show="showMenu"></left-menu>
<div ref="body" class="body">
<Header></Header>
<Header v-show="showHeader"></Header>
<container v-if="containerShow" ref="container"></container>
</div>
<!--web-ssh-->
@@ -46,6 +46,12 @@ export default {
},
loading () {
return this.$store.getters.getHomeLoading
},
showMenu () {
return !this.$route.meta.hideMenu
},
showHeader () {
return !this.$route.meta.hideHeader
}
},
methods: {
@@ -54,6 +60,7 @@ export default {
}
},
mounted () {
console.log(this.$router, this.$route)
},
destroyed () {
localStorage.removeItem('moduleProjectId')

View File

@@ -225,6 +225,11 @@ export default {
detailViewTopSearch
},
mixins: [dataListMixin, detailViewMixin, routerPathParams],
computed: {
externalTerminal () {
return this.$store.getters.getExternalTerminal
}
},
data () {
return {
url: 'asset/asset',
@@ -467,7 +472,12 @@ export default {
host: row.manageIp,
port: row.authProtocolPort
}
this.$store.commit('addConsole', consoleParam)
if (!this.externalTerminal) {
this.$store.dispatch('dispatchExternalTerminal', consoleParam)
} else {
this.$store.dispatch('dispatchOpenExternalTerminalWindow', consoleParam)
}
// this.$store.commit('addConsole', consoleParam)
},
duplicate (row) {
this.$get(`${this.url}/${row.id}`).then(response => {

View File

@@ -3,7 +3,8 @@ import bus from '../../../../libs/bus'
import vm from '../../../../entrance/app/main'
import exportHtml from '../../../../entrance/exportHtml/exportHtml'
// const vm = window.dataJson ? exportHtml : app
window.onload = function () {
window.addEventListener('load', function () {
console.log(123213)
if (!window.dataJson) {
commonOption.toolbox.feature.dataZoom.title.zoom = vm.$i18n.t('overall.toolBox.zoom')
commonOption.toolbox.feature.dataZoom.title.back = vm.$i18n.t('overall.toolBox.back')
@@ -13,7 +14,7 @@ window.onload = function () {
commonOption.toolbox.feature.dataZoom.title.back = exportHtml.$i18n.t('overall.toolBox.back')
commonOption.toolbox.feature.magicType.title.stack = exportHtml.$i18n.t('overall.toolBox.stack')
}
}
})
const bgColorList = ['#b3424a', '#7bbfea', '#f05b72', '#596032', '#bd6758',
'#cd9a5b', '#918597', '#70a19f', '#005344', '#FF00FF',
'#f7acbc', '#5f5d46', '#66ffff', '#ccFF66', '#f47920',

View File

@@ -8,7 +8,7 @@ import VueResource from 'vue-resource'
import bus from '@/libs/bus'
Vue.use(VueResource)
const loginWhiteList = ['/setup', '/sys/license/upload', '/sys/license/state', '/sys/appearance', '/i18n'] // 免登陆白名单
const loginWhiteList = ['/setup', '/sys/license/upload', '/sys/license/state', '/sys/appearance', '/i18n', '/externalTerminal'] // 免登陆白名单
const permissionWhiteList = ['/profile', '/menu', ...loginWhiteList] // 权限白名单
router.beforeEach((to, from, next) => {
if (window.entrance) {

View File

@@ -221,6 +221,14 @@ export default new Router({
component: resolve => require(['@/components/page/tool/trace'], resolve)
}
]
},
{
path: '/externalTerminal',
component: resolve => require(['@/components/cli/terminal'], resolve),
meta: {
hideHeader: true,
hideMenu: true
}
}
]
})

View File

@@ -3,7 +3,10 @@ const terminalFile = {
fileList: [],
uploadItem: {},
updateUuid: '', // 根据uuid的变化 判断页面是否需要刷新
updateIndex: 1
updateIndex: 1,
externalTerminal: false,
messageFunction: false,
externalTerminalWindow: ''
},
mutations: {
setFileList (state, arr) {
@@ -18,6 +21,48 @@ const terminalFile = {
setUploadItem (state, item) {
state.uploadItem = item
},
setExternalTerminal (state, item) { // 第一次 关闭或者打开 termail
state.externalTerminal = !state.externalTerminal
if (!state.messageFunction) {
state.messageFunction = true
window.addEventListener('message', function (e) {
console.log(e)
const data = JSON.parse(e.data) // e.data 里面有自己所传的所有参数 可以根据参数做自己的判断
if (data.close) {
state.externalTerminal = false
}
})
}
if (state.externalTerminal) {
state.externalTerminalWindow = window.open('/ui/externalTerminal', 'terminal')
} else {
state.externalTerminalWindow = ''
}
if (item && state.externalTerminal) {
console.log('onloda', state.externalTerminalWindow)
state.externalTerminalWindow.addEventListener('load', () => {
console.log(123123123123123)
setTimeout(() => {
console.log('time')
state.externalTerminalWindow.postMessage(
JSON.stringify(item)
)
}, 3000)
})
}
},
openExternalTerminalWindow (state, item) { // 后续打开 termail
if (state.externalTerminalWindow) {
window.open('', 'terminal')
} else {
state.externalTerminalWindow = window.open('/ui/externalTerminal', 'terminal')
}
if (item) {
state.externalTerminalWindow.postMessage(
JSON.stringify(item)
)
}
},
setUpdateUuid (state, uuid) {
state.updateUuid = uuid
state.updateIndex++
@@ -38,6 +83,9 @@ const terminalFile = {
},
getUpdateIndex (state) {
return state.updateIndex
},
getExternalTerminal (state) {
return state.externalTerminal
}
},
actions: {
@@ -55,6 +103,13 @@ const terminalFile = {
},
upDateConsole (store, uuid) {
store.commit('setUpdateUuid', uuid)
},
// 打开外部termail
dispatchExternalTerminal (store, item) { // 第一次打开termail
store.commit('setExternalTerminal', item)
},
dispatchOpenExternalTerminalWindow (store, item) { // 后续操作termail
store.commit('openExternalTerminalWindow', item)
}
}
}