mirror of
https://github.com/jumpserver/lina.git
synced 2025-08-07 09:45:02 +00:00
perf: file transfer
This commit is contained in:
parent
ada6479177
commit
4e79074cef
@ -16,6 +16,3 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
|
@ -1,42 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="term-wrapper">
|
<div style="position: relative;">
|
||||||
<div class="term-header">
|
<div v-if="showToolBar" class="actions">
|
||||||
<div class="header-text">{{ $tc('Output') }}</div>
|
|
||||||
<div
|
<div
|
||||||
v-if="executionInfo.status"
|
v-for="(item,index) in toolbar"
|
||||||
class="header-status"
|
:key="index"
|
||||||
|
style="display: inline-block"
|
||||||
>
|
>
|
||||||
<span class="status-item">
|
<el-tooltip :content="item.tip" :open-delay="500">
|
||||||
<span>{{ $tc('Status') }}: </span>
|
<el-button
|
||||||
<span
|
v-if="!item.isScrollButton || showScrollButton"
|
||||||
:class="{'status_success':executionInfo.status==='success',
|
size="mini"
|
||||||
'status_warning':executionInfo.status==='timeout',
|
type="default"
|
||||||
'status_danger':executionInfo.status==='failed'
|
@click="item.callback()"
|
||||||
}"
|
>
|
||||||
>{{ $tc('' + executionInfo.status) }}</span>
|
<svg-icon :icon-class="item.icon" />
|
||||||
</span>
|
</el-button>
|
||||||
<span class="status-item">
|
</el-tooltip>
|
||||||
<span>{{ $tc('TimeDelta') }}: </span>
|
|
||||||
<span>{{ executionInfo.timeCost.toFixed(2) }}s</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="showToolBar" class="actions">
|
|
||||||
<div
|
|
||||||
v-for="(item,index) in toolbar"
|
|
||||||
:key="index"
|
|
||||||
class="action-item"
|
|
||||||
>
|
|
||||||
<el-tooltip :content="item.tip" :open-delay="500">
|
|
||||||
<el-button
|
|
||||||
v-if="!item.isScrollButton || showScrollButton"
|
|
||||||
size="mini"
|
|
||||||
type="primary"
|
|
||||||
@click="item.callback()"
|
|
||||||
>
|
|
||||||
<svg-icon :icon-class="item.icon" />
|
|
||||||
</el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="terminal" ref="terminal" class="xterm" />
|
<div id="terminal" ref="terminal" class="xterm" />
|
||||||
@ -62,10 +41,6 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => {
|
default: () => {
|
||||||
}
|
}
|
||||||
},
|
|
||||||
executionInfo: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -85,13 +60,17 @@ export default {
|
|||||||
{
|
{
|
||||||
tip: this.$tc('ScrollToTop'),
|
tip: this.$tc('ScrollToTop'),
|
||||||
icon: 'arrow-up',
|
icon: 'arrow-up',
|
||||||
callback: this.scrollToTop,
|
callback: () => {
|
||||||
|
this.xterm.scrollToTop()
|
||||||
|
},
|
||||||
isScrollButton: true
|
isScrollButton: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tip: this.$tc('ScrollToBottom'),
|
tip: this.$tc('ScrollToBottom'),
|
||||||
icon: 'arrow-down',
|
icon: 'arrow-down',
|
||||||
callback: this.scrollToBottom,
|
callback: () => {
|
||||||
|
this.xterm.scrollToBottom()
|
||||||
|
},
|
||||||
isScrollButton: true
|
isScrollButton: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -136,113 +115,34 @@ export default {
|
|||||||
},
|
},
|
||||||
checkScroll(position) {
|
checkScroll(position) {
|
||||||
this.showScrollButton = position > 0
|
this.showScrollButton = position > 0
|
||||||
},
|
|
||||||
scrollToTop() {
|
|
||||||
// 不能改,只有这么写才能保证上箭头和下箭头不消失
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.xterm.scrollToTop()
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
this.showScrollButton = true
|
|
||||||
})
|
|
||||||
},
|
|
||||||
scrollToBottom() {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.showScrollButton = true
|
|
||||||
this.xterm.scrollToBottom()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped>
|
||||||
$header-bg-color: #F5F6F7;
|
.xterm {
|
||||||
$actions-hover-bg-color: #d2d2d2;
|
overflow: auto;
|
||||||
|
padding-left: 5px;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
.term-wrapper {
|
.actions {
|
||||||
position: relative;
|
text-align: right;
|
||||||
|
background-color: #FFF;
|
||||||
|
padding-right: 5px;
|
||||||
|
padding-top: 2px
|
||||||
|
}
|
||||||
|
|
||||||
.term-header {
|
.el-button {
|
||||||
position: relative;
|
border: none;
|
||||||
display: flex;
|
padding: 2px;
|
||||||
align-items: center;
|
font-size: 14px;
|
||||||
height: 45px;
|
width: 26px;
|
||||||
padding-left: 15px;
|
height: 26px;
|
||||||
background-color: $header-bg-color;
|
color: #888;
|
||||||
|
background-color: transparent;
|
||||||
.header-text {
|
margin-left: 2px;
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-status {
|
|
||||||
margin-left: 10px;
|
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
.status-item {
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
padding-left: 25px;
|
|
||||||
|
|
||||||
.status_success {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status_warning {
|
|
||||||
color: var(--color-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status_danger {
|
|
||||||
color: var(--color-danger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
margin-right: 10px;
|
|
||||||
background-color: $header-bg-color;
|
|
||||||
|
|
||||||
.action-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.el-button {
|
|
||||||
border: none;
|
|
||||||
padding: 2px;
|
|
||||||
font-size: 14px;
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
color: #888;
|
|
||||||
background-color: transparent;
|
|
||||||
margin-left: 2px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $actions-hover-bg-color !important;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
background-color: $actions-hover-bg-color !important;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm {
|
|
||||||
overflow: auto;
|
|
||||||
padding: 10px 0 0 20px;
|
|
||||||
background-color: #FFFFFF;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -139,7 +139,6 @@ $cursor: #fff;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* reset element-ui css */
|
|
||||||
.login-container {
|
.login-container {
|
||||||
.el-input {
|
.el-input {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
/>
|
/>
|
||||||
<span v-if="executionInfo.status" style="float: right" />
|
<span v-if="executionInfo.status" style="float: right" />
|
||||||
<div class="xterm-container">
|
<div class="xterm-container">
|
||||||
<Term
|
<QuickJobTerm
|
||||||
ref="xterm"
|
ref="xterm"
|
||||||
:show-tool-bar="true"
|
:show-tool-bar="true"
|
||||||
:xterm-config="xtermConfig"
|
:xterm-config="xtermConfig"
|
||||||
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import AssetTreeTable from '@/components/Apps/AssetTreeTable'
|
import AssetTreeTable from '@/components/Apps/AssetTreeTable'
|
||||||
import Term from '@/components/Widgets/Term'
|
import QuickJobTerm from '@/views/ops/Adhoc/components/QuickJobTerm.vue'
|
||||||
import CodeEditor from '@/components/Form/FormFields/CodeEditor'
|
import CodeEditor from '@/components/Form/FormFields/CodeEditor'
|
||||||
import Page from '@/layout/components/Page'
|
import Page from '@/layout/components/Page'
|
||||||
import AdhocOpenDialog from './AdhocOpenDialog.vue'
|
import AdhocOpenDialog from './AdhocOpenDialog.vue'
|
||||||
@ -52,7 +52,7 @@ export default {
|
|||||||
AdhocOpenDialog,
|
AdhocOpenDialog,
|
||||||
AssetTreeTable,
|
AssetTreeTable,
|
||||||
Page,
|
Page,
|
||||||
Term,
|
QuickJobTerm,
|
||||||
CodeEditor
|
CodeEditor
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
251
src/views/ops/Adhoc/components/QuickJobTerm.vue
Normal file
251
src/views/ops/Adhoc/components/QuickJobTerm.vue
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
<template>
|
||||||
|
<div class="term-wrapper">
|
||||||
|
<div class="term-header">
|
||||||
|
<div class="header-text">{{ $tc('Output') }}</div>
|
||||||
|
<div
|
||||||
|
v-if="executionInfo.status"
|
||||||
|
class="header-status"
|
||||||
|
>
|
||||||
|
<span class="status-item">
|
||||||
|
<span>{{ $tc('Status') }}: </span>
|
||||||
|
<span
|
||||||
|
:class="{'status_success':executionInfo.status==='success',
|
||||||
|
'status_warning':executionInfo.status==='timeout',
|
||||||
|
'status_danger':executionInfo.status==='failed'
|
||||||
|
}"
|
||||||
|
>{{ $tc('' + executionInfo.status) }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="status-item">
|
||||||
|
<span>{{ $tc('TimeDelta') }}: </span>
|
||||||
|
<span>{{ executionInfo.timeCost.toFixed(2) }}s</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="showToolBar" class="actions">
|
||||||
|
<div
|
||||||
|
v-for="(item,index) in toolbar"
|
||||||
|
:key="index"
|
||||||
|
class="action-item"
|
||||||
|
>
|
||||||
|
<el-tooltip :content="item.tip" :open-delay="500">
|
||||||
|
<el-button
|
||||||
|
v-if="!item.isScrollButton || showScrollButton"
|
||||||
|
size="mini"
|
||||||
|
type="primary"
|
||||||
|
@click="item.callback()"
|
||||||
|
>
|
||||||
|
<svg-icon :icon-class="item.icon" />
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="terminal" ref="terminal" class="xterm" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import 'xterm/css/xterm.css'
|
||||||
|
import { Terminal } from 'xterm'
|
||||||
|
import { FitAddon } from 'xterm-addon-fit'
|
||||||
|
import { downloadText } from '@/utils/common'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Term',
|
||||||
|
props: {
|
||||||
|
showToolBar: {
|
||||||
|
type: [Boolean, Object],
|
||||||
|
default: () => {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xtermConfig: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
executionInfo: {
|
||||||
|
type: Object,
|
||||||
|
// eslint-disable-next-line vue/require-valid-default-prop
|
||||||
|
default: {
|
||||||
|
status: 'success'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
xterm: new Terminal(Object.assign({
|
||||||
|
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
|
||||||
|
lineHeight: 1.2,
|
||||||
|
fontSize: 13,
|
||||||
|
rightClickSelectsWord: true,
|
||||||
|
theme: {
|
||||||
|
background: '#fff',
|
||||||
|
foreground: '#000',
|
||||||
|
selection: '#363535'
|
||||||
|
}
|
||||||
|
}, this.xtermConfig)),
|
||||||
|
toolbar: [
|
||||||
|
{
|
||||||
|
tip: this.$tc('ScrollToTop'),
|
||||||
|
icon: 'arrow-up',
|
||||||
|
callback: this.scrollToTop,
|
||||||
|
isScrollButton: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tip: this.$tc('ScrollToBottom'),
|
||||||
|
icon: 'arrow-down',
|
||||||
|
callback: this.scrollToBottom,
|
||||||
|
isScrollButton: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tip: this.$tc('ClearScreen'),
|
||||||
|
icon: 'refresh',
|
||||||
|
callback: () => {
|
||||||
|
this.xterm.reset()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tip: this.$tc('Export'),
|
||||||
|
icon: 'download',
|
||||||
|
callback: () => {
|
||||||
|
this.xterm.selectAll()
|
||||||
|
const text = this.xterm.getSelection()
|
||||||
|
const filename = `${this.xtermConfig?.type}_${this.xtermConfig?.taskId}.log`
|
||||||
|
downloadText(text, filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
showScrollButton: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function() {
|
||||||
|
const terminalContainer = this.$refs.terminal
|
||||||
|
const fitAddon = new FitAddon()
|
||||||
|
this.xterm.loadAddon(fitAddon)
|
||||||
|
this.xterm.open(terminalContainer)
|
||||||
|
fitAddon.fit()
|
||||||
|
this.xterm.scrollToBottom()
|
||||||
|
this.xterm.onScroll(this.checkScroll)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.xterm.dispose()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
reset: function() {
|
||||||
|
this.xterm.reset()
|
||||||
|
},
|
||||||
|
write: function(val) {
|
||||||
|
this.xterm.write(val)
|
||||||
|
},
|
||||||
|
checkScroll(position) {
|
||||||
|
this.showScrollButton = position > 0
|
||||||
|
},
|
||||||
|
scrollToTop() {
|
||||||
|
// 不能改,只有这么写才能保证上箭头和下箭头不消失
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.xterm.scrollToTop()
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showScrollButton = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
scrollToBottom() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showScrollButton = true
|
||||||
|
this.xterm.scrollToBottom()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
$header-bg-color: #F5F6F7;
|
||||||
|
$actions-hover-bg-color: #d2d2d2;
|
||||||
|
|
||||||
|
.term-wrapper {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.term-header {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 45px;
|
||||||
|
padding-left: 15px;
|
||||||
|
background-color: $header-bg-color;
|
||||||
|
|
||||||
|
.header-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-status {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
padding-left: 25px;
|
||||||
|
|
||||||
|
.status_success {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status_warning {
|
||||||
|
color: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status_danger {
|
||||||
|
color: var(--color-danger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 10px;
|
||||||
|
background-color: $header-bg-color;
|
||||||
|
|
||||||
|
.action-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
border: none;
|
||||||
|
padding: 2px;
|
||||||
|
font-size: 14px;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
color: #888;
|
||||||
|
background-color: transparent;
|
||||||
|
margin-left: 2px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $actions-hover-bg-color !important;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: $actions-hover-bg-color !important;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 10px 0 0 20px;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -121,7 +121,7 @@
|
|||||||
/>
|
/>
|
||||||
<div style="height: 2px" />
|
<div style="height: 2px" />
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex;margin-top:10px;justify-content: space-between" />
|
<div style="display: flex; margin-top:10px; justify-content: space-between" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</AssetTreeTable>
|
</AssetTreeTable>
|
||||||
@ -167,7 +167,9 @@ export default {
|
|||||||
type: 'primary'
|
type: 'primary'
|
||||||
},
|
},
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.execute()
|
setTimeout(() => {
|
||||||
|
this.execute()
|
||||||
|
}, 300)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
runAsInput: {
|
runAsInput: {
|
||||||
|
Loading…
Reference in New Issue
Block a user