feat: terminal日志回放,节前提交一半关机没提成功

This commit is contained in:
chenjinsong
2021-02-18 10:13:52 +08:00
parent cb9e0fcd60
commit aee43ea920
11 changed files with 827 additions and 133 deletions

View File

@@ -43,6 +43,7 @@ $dropdown-hover-background-color: #fafafa; //下拉鼠标悬停背景色
$danger-color: #DE5D3F; //全局警告色红色 $danger-color: #DE5D3F; //全局警告色红色
$success-color: #23BF9A; //全局正常色绿色 $success-color: #23BF9A; //全局正常色绿色
$warning-color: $btn-light-txt-color-hover-new; //全局警告橙色
$suspended-color: #9e9c98; //全局停用色灰色 $suspended-color: #9e9c98; //全局停用色灰色
$left-menu-bgcolor:#FFF; $left-menu-bgcolor:#FFF;

View File

@@ -917,8 +917,8 @@ li{
padding:2px 5px; padding:2px 5px;
border-radius: 4px; border-radius: 4px;
} }
.nz-table td.success .cell>span { .nz-table td.warning .cell>span {
background-color: $success-color; background-color: $warning-color;
color: white; color: white;
padding:2px 5px; padding:2px 5px;
border-radius: 4px; border-radius: 4px;

View File

@@ -16,20 +16,24 @@
<div class="window-control-btn" @click="closeSubList" :id="from+'-bottom-close'"><i class="nz-icon nz-icon-close"></i></div> <div class="window-control-btn" @click="closeSubList" :id="from+'-bottom-close'"><i class="nz-icon nz-icon-close"></i></div>
</div> </div>
<!------TAB区------> <!------TAB区------>
<!--机柜--> <!--机柜-->
<cabinet-tab :obj="obj" @changeTab="changeTab" v-if="from == $CONSTANTS.fromRoute.dc && targetTab == 'cabinet'" v-show="subResizeShow"></cabinet-tab> <cabinet-tab :obj="obj" @changeTab="changeTab" v-if="from == $CONSTANTS.fromRoute.dc && targetTab == 'cabinet'" v-show="subResizeShow"></cabinet-tab>
<!--告警信息--> <!--告警信息-->
<alert-message-tab :from="from" :obj="obj" @changeTab="changeTab" v-if="((from == $CONSTANTS.fromRoute.rule || from == $CONSTANTS.fromRoute.asset || from == $CONSTANTS.fromRoute.endpoint) && targetTab == 'alertMessage')" v-show="subResizeShow"></alert-message-tab> <alert-message-tab :from="from" :obj="obj" @changeTab="changeTab" v-if="((from == $CONSTANTS.fromRoute.rule || from == $CONSTANTS.fromRoute.asset || from == $CONSTANTS.fromRoute.endpoint) && targetTab == 'alertMessage')" v-show="subResizeShow"></alert-message-tab>
<!--asset页的endpoint列表--> <!--asset页的endpoint列表-->
<endpoint-tab :from="from" :obj="obj" @changeTab="changeTab" v-if="from == $CONSTANTS.fromRoute.asset && targetTab == $CONSTANTS.fromRoute.endpoint" v-show="subResizeShow"></endpoint-tab> <endpoint-tab :from="from" :obj="obj" @changeTab="changeTab" v-if="from == $CONSTANTS.fromRoute.asset && targetTab == $CONSTANTS.fromRoute.endpoint" v-show="subResizeShow"></endpoint-tab>
<!--endpoint-query--> <!--endpoint-query-->
<endpoint-query-tab :from="from" :obj="obj" @changeTab="changeTab" ref="endpointQuery" v-if="(from == $CONSTANTS.fromRoute.endpoint && targetTab == 'endpointQuery')" v-show="subResizeShow"></endpoint-query-tab> <endpoint-query-tab :from="from" :obj="obj" @changeTab="changeTab" ref="endpointQuery" v-if="(from == $CONSTANTS.fromRoute.endpoint && targetTab == 'endpointQuery')" v-show="subResizeShow"></endpoint-query-tab>
<!-- model-panel/asset-detail/project-overview的panel--> <!-- model-panel/asset-detail/project-overview的panel-->
<panel-tab :from="from" :obj="obj" ref="panelTab" v-if="(from == $CONSTANTS.fromRoute.model || from == $CONSTANTS.fromRoute.asset || from == $CONSTANTS.fromRoute.project || from == $CONSTANTS.fromRoute.rule || from == $CONSTANTS.fromRoute.endpoint) && targetTab == 'panel'" v-show="subResizeShow" <panel-tab :from="from" :obj="obj" ref="panelTab" v-if="(from == $CONSTANTS.fromRoute.model || from == $CONSTANTS.fromRoute.asset || from == $CONSTANTS.fromRoute.project || from == $CONSTANTS.fromRoute.rule || from == $CONSTANTS.fromRoute.endpoint) && targetTab == 'panel'" v-show="subResizeShow"
@changeTab="changeTab" :targetTab.sync="targetTab" :detail="detail"></panel-tab> @changeTab="changeTab" :targetTab.sync="targetTab" :detail="detail"></panel-tab>
<!--terminal-log的记录和回放-->
<terminal-log-record-tab :from="from" :obj="obj" @changeTab="changeTab" ref="reminalLogRecordTab" v-if="from == $CONSTANTS.fromRoute.terminalLog && targetTab == 'record'"></terminal-log-record-tab>
<terminal-log-replay-tab :from="from" :obj="obj" @changeTab="changeTab" ref="reminalLogReplayTab" v-if="from == $CONSTANTS.fromRoute.terminalLog && targetTab == 'replay'"></terminal-log-replay-tab>
</div> </div>
</div> </div>
</template> </template>
@@ -40,6 +44,8 @@
import endpointQueryTab from "./tabs/endpointQueryTab"; import endpointQueryTab from "./tabs/endpointQueryTab";
import endpointTab from "./tabs/endpointTab"; import endpointTab from "./tabs/endpointTab";
import panelTab from "./tabs/panelTab"; import panelTab from "./tabs/panelTab";
import terminalLogRecordTab from "./tabs/terminalLogRecordTab";
import terminalLogReplayTab from "./tabs/terminalLogReplayTab";
export default { export default {
name: "bottomBox", name: "bottomBox",
@@ -49,6 +55,8 @@
'endpoint-query-tab': endpointQueryTab, 'endpoint-query-tab': endpointQueryTab,
'endpoint-tab': endpointTab, 'endpoint-tab': endpointTab,
'panel-tab': panelTab, 'panel-tab': panelTab,
terminalLogRecordTab,
terminalLogReplayTab
}, },
props: { props: {
isFullScreen: false, //是否全屏 isFullScreen: false, //是否全屏

View File

@@ -0,0 +1,252 @@
<template>
<div style="height: 100%;">
<div class="sub-top-tools">
<div class="sub-list-tabs">
<div class="sub-list-tab-title">ID{{obj.id}}</div><div
@click="changeTab('replay')" class="sub-list-tab">{{$t("config.terminallog.replay")}}</div><div
class="sub-list-tab sub-list-tab-active">{{$t("config.terminallog.record.record")}}</div>
</div>
</div>
<div class="record-container">
<div class="record-container--record">
<div class="record--title">{{$t('config.terminallog.record.history')}}</div>
<div class="record--list">
<template v-for="(record, index) in records">
<template v-for="item in record.list">
<div class="detail--time"><span>{{calcTime(item.time)}}</span></div>
<div class="detail--cmd"><span :class="matchBgColor(item.cmd)">{{item.cmd}}</span></div>
</template>
</template>
</div>
<div class="record--more" v-if="hasNext">
<span @click="loadMore" class="nz-btn nz-btn-size-small nz-btn-style-light"><i class="nz-icon nz-icon-arrow-down"></i></span>
</div>
</div>
<div class="record-container--record record-container--record__tip">
<div class="record--title">{{$t('config.terminallog.record.legendTip')}}</div>
<div class="record--list">
<div class="detail--time"><span>yyyy-MM-dd HH:mm:ss</span></div>
<div class="detail--cmd"><span class="detail--cmd__red">{{$t("config.terminallog.record.dangerTip")}}</span></div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "terminalLogRecordTab",
components: {
},
props: {
obj: Object, //关联的实体对象
},
computed: {
calcTime() {
return function(time) {
if (this.obj.startTime) {
let startTime = new Date(this.obj.startTime).getTime();
if (startTime) {
let thisTime = startTime+time;
return this.dateFormat(thisTime);
}
}
return "-";
}
},
matchBgColor() {
return function(cmd) {
let className = "";
let dangerCmd = this.$CONSTANTS.terminalLog.dangerCmd;
let infoCmd = this.$CONSTANTS.terminalLog.infoCmd;
let cmdSplit = cmd.split(" ");
if (this.intersection(infoCmd, cmdSplit).length > 0) {
className = "detail--cmd__green";
}
if (this.intersection(dangerCmd, cmdSplit).length > 0) {
className = "detail--cmd__red";
}
return className;
}
},
hasNext() {
if (this.records.length > 0) {
return this.records[this.records.length-1].hasNext;
}
return false;
}
},
data() {
return {
filter: {
uuid: this.obj.uuid,
cmd: "",
time: "",
size: 200
},
records: [ // 加载更多时有多个record否则只有一个
],
/*record: { // cmd记录含hasNext、time(最后一条时间)、total和list
total: 4,
hasNext: true,
time: 9889,
list: [
{
id: 1,
uuid: 1,
time: 1024,
cmd: "ls -ef|grep java"
},
{
id: 2,
uuid: 2,
time: 3824,
cmd: "su root ls -ef|grep java|afaewrafaser aolewr | awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|ls -ef|grep java|afaewrafaser aolewr | awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|ls -ef|grep java|afaewrafaser aolewr | awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|ls -ef|grep java|afaewrafaser aolewr | awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|ls -ef|grep java|afaewrafaser aolewr | awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|ls -ef|grep java|afaewrafaser aolewr | awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|ls -ef|grep java|afaewrafaser aolewr | awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|awk fajpoerja;welkrjfa;wle|"
},
{
id: 3,
uuid: 3,
time: 7740,
cmd: "rm -rf /"
},
{
id: 4,
uuid: 4,
time: 9889,
cmd: "exit"
},
]
}*/
}
},
methods: {
initData(filter) {
this.$get("/terminal/cmd", filter).then(res => {
if (res.code === 200) {
this.records.push(res.data);
} else {
this.$message.error(res.msg);
}
});
},
loadMore() {
let filter = Object.assign({}, this.filter);
filter.time = this.records[this.records.length-1].time;
this.initData(filter);
},
// 切换tab
changeTab(tab) {
this.$emit('changeTab', tab);
},
intersection(a, b) {
let s = new Set(b);
return a.filter(x => s.has(x));
},
dateFormat(time) {
if (!time) {
return '-';
}
let date = new Date(time);
let year = date.getFullYear();
let month = date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1;
let day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate();
let hours = date.getHours() < 10 ? "0" + date.getHours() : date.getHours();
let minutes = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();
let seconds = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
return year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds;
},
formatUpdateTime(date) {
let time=new Date(date);
let hours=time.getHours()>9?time.getHours():'0'+time.getHours();
let minutes=time.getMinutes()>9?time.getMinutes():'0'+time.getMinutes();
return hours+':'+minutes;
},
},
watch: {
obj: {
immediate: true,
deep: true,
handler(n) {
}
},
},
mounted() {
this.initData(this.filter);
},
}
</script>
<style lang="scss">
.record-container {
height: calc(100% - 50px);
overflow: auto;
}
.record-container--record {
background-color: white;
width: calc(50% - 14px);
padding: 16px 15px 14px 15px;
border: 1px solid #d8dce1;
box-sizing: border-box;
min-height: 100px;
.record--title {
color: #333;
font-size: 14px;
font-weight: bold;
margin-bottom: 12px;
}
.record--list {
display: grid;
gap: 6px 12px;
grid-template-columns: 150px auto;
font-size: 12px;
color: #333;
padding-left: 16px;
.detail--time {
line-height: 18px;
grid-column: 1/span 1;
display: flex;
align-items: flex-start;
span {
background-color: #ECECEC;
border-radius: 3px;
padding: 2px 5px;
}
}
.detail--cmd {
line-height: 18px;
grid-column: 2/span 1;
display: flex;
span {
border-radius: 3px;
padding: 2px 5px;
}
}
.detail--cmd__green {
background-color: #B4FDB1;
}
.detail--cmd__red {
background-color: #FFD2C2;
}
}
.record--more {
text-align: center;
}
}
.record-container--record.record-container--record__tip {
margin-top: 10px;
min-height: unset;
}
</style>

View File

@@ -0,0 +1,268 @@
<template>
<div style="height: 100%;">
<div class="sub-top-tools">
<div class="sub-list-tabs">
<div class="sub-list-tab-title">ID{{obj.id}}</div><div
class="sub-list-tab sub-list-tab-active">{{$t("config.terminallog.replay")}}</div><div
@click="changeTab('record')" class="sub-list-tab">{{$t("config.terminallog.record.record")}}</div>
</div>
</div>
<div class="replay-container">
<div class="replay-operate">
<button @click="pause" class="nz-btn nz-btn-style-light nz-btn-size-large">暂停</button>
<button @click="play" class="nz-btn nz-btn-style-light nz-btn-size-large">播放</button>
<button @click="restart" class="nz-btn nz-btn-style-light nz-btn-size-large">重新播放</button>
<el-tag>{{progress}}</el-tag>
</div>
<div class="replay-console">
<div :id="obj.uuid" class="replay-terminal"></div>
</div>
</div>
</div>
</template>
<script>
import Terminal from '../../js/Xterm';
export default {
name: "terminalLogReplayTab",
components: {
Terminal
},
props: {
obj: Object, //关联的实体对象
},
data() {
return {
filter: {
size: 1,
uuid: ""
},
operate: {
pause: false, // 是否暂停
},
terminal: null,
recordData: [],
isNeedStop: false, // 是否需要停止
playedCount: 0, // 当前已经播放完的数量
isPlaying: true, // 是否正在播放
isFinish: false, //是否已结束
recordTick: 50, // 输出间隔时间ms默认50
playerTimer: null, // 定时器
playerCurrentTime: 0, // 当前时间进度
speedTable: [ // 快进倍速选项
{speed: 1, name: 'Normal'},
{speed: 2, name: 'x2'},
{speed: 4, name: 'x4'},
{speed: 8, name: 'x8'},
{speed: 16, name: 'x16'}
],
speedOffset: 0, // 快进倍数index
progress: 0, // 进度条进度
needSkip: false, // 需要时间刻度即是否跳过无操作时间为true时表示需要即不跳过无操作时间
timeUsed: 0,
}
},
methods: {
// 切换tab
changeTab(tab) {
this.$emit('changeTab', tab);
},
getReplayData() {
return new Promise(resolve => {
this.$get("/terminal/record", this.filter).then(res => {
if (res.code === 200) {
this.recordData = res.data.list;
this.timeUsed = res.data.endTime;
}
resolve();
});
});
},
initAndPlay() {
// terminal初始化
if (!this.terminal) {
this.terminal = new Terminal(
{
cols: 70,
rows: 28
}
);
} else {
this.terminal.destroy();
}
setTimeout(() => {
this.terminal.open(document.getElementById(this.obj.uuid));
this.terminal.resize(70, 28);
this.$nextTick(() => {
if (this.recordData.length > 0) {
this.isNeedStop = false;
this.isPlaying = true;
this.isFinished = false;
this.playedCount = 0;
this.doPlay();
} else {
let req = this.getReplayData();
req.then(() => {
if (this.recordData.length > 0) {
// 请求得到数据后,开始播放
this.isNeedStop = false;
this.isPlaying = true;
this.isFinished = false;
this.playedCount = 0;
this.doPlay();
}
});
}
});
}, 800);
},
/*原理利用setTimeout和数据的时间偏移量来模拟实现视频播放效果*/
doPlay() {
if (this.isNeedStop) {
this.isPlaying = false;
return;
}
if (this.recordData.length <= this.playedCount) {
this.playerTimer = setTimeout(this.doPlay, this.recordTick);
return;
}
this.playerCurrentTime += this.recordTick * this.speedTable[this.speedOffset].speed;
let recordTick = this.recordTick;
let playData;
for (let i = this.playedCount; i < this.recordData.length; i++) {
if (this.isNeedStop) {
break;
}
playData = this.recordData[i];
if (playData.t < this.playerCurrentTime) {
this.terminal.write(playData.c);
if ((this.playedCount + 1) === this.recordData.length) {
//播放完成
this.progress = 100;
this.isFinished = true;
this.isPlaying = false;
return;
} else {
this.playedCount++;
}
} else {
break;
}
}
if (this.isNeedStop) {
return;
}
if (this.needSkip) {
if (playData.t - this.playerCurrentTime > 800) {
this.playerCurrentTime = playData.t; // - this.record_tick * this.speed_table[this.speed_offset].speed;
recordTick = 800;
}
}
// 同步进度条
this.progress = parseInt(this.playerCurrentTime * 100 / this.timeUsed);
if (this.playedCount >= this.recordData.length) {
this.progress = 100;
//播放完成
this.isFinished = true;
this.isPlaying = false;
} else {
if (!this.isNeedStop)
this.playerTimer = setTimeout(this.doPlay, recordTick);
}
},
play() {
if (this.isPlaying) {
return;
}
if (this.isFinished) {
this.restart();
return;
}
this.isNeedStop = false;
this.isPlaying = true;
this.playerTimer = setTimeout(this.doPlay, this.recordTick);
},
pause() {
if (this.playerTimer) {
clearTimeout(this.playerTimer);
}
this.isNeedStop = true;
this.isPlaying = false;
},
restart() {
if (this.playerTimer) {
clearTimeout(this.playerTimer);
}
this.playerCurrentTime = 0;
this.initAndPlay();
},
dateFormat(time) {
if (!time) {
return '-';
}
let date = new Date(time * 1000);
let year = date.getFullYear();
let month = date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1;
let day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate();
let hours = date.getHours() < 10 ? "0" + date.getHours() : date.getHours();
let minutes = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();
let seconds = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
return year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds;
},
formatUpdateTime(date) {
let time=new Date(date);
let hours=time.getHours()>9?time.getHours():'0'+time.getHours();
let minutes=time.getMinutes()>9?time.getMinutes():'0'+time.getMinutes();
return hours+':'+minutes;
},
},
watch: {
// 入口监听terminal session的变化开始功能
obj: {
immediate: true,
deep: true,
handler(n) {
if (n.uuid) {
this.filter.uuid = n.uuid;
this.initAndPlay();
}
}
},
},
mounted() {
},
}
</script>
<style lang="scss">
.replay-console {
width: 660px;
height: 480px;
padding: 10px 4px 10px 10px;
background-color: black;
}
</style>

View File

@@ -149,6 +149,18 @@ export const setting = {
] ]
}; };
export const terminalLog = {
status: {
0: i18n.t('config.terminallog.statusItem.connecting'),
1: i18n.t('config.terminallog.statusItem.connectionFailed'),
2: i18n.t('config.terminallog.statusItem.over'),
3: i18n.t('config.terminallog.statusItem.kickedOut'),
4: i18n.t('config.terminallog.statusItem.unknownError'),
},
dangerCmd: ['chmod', 'chown', 'kill', 'rm', 'su', 'sudo'],
infoCmd: ['exit']
};
//公共组件的跳转来源 //公共组件的跳转来源
export const fromRoute = { export const fromRoute = {
panel: "panel", panel: "panel",
@@ -160,5 +172,6 @@ export const fromRoute = {
dc: "dc", dc: "dc",
endpoint: "endpoint", endpoint: "endpoint",
project: "project", project: "project",
endpointQuery: "endpointQuery" endpointQuery: "endpointQuery",
terminalLog: "terminal"
}; };

View File

@@ -284,7 +284,17 @@ export function stringTimeParseToUnix(stringTime){
let time=new Date(stringTime).getTime(); let time=new Date(stringTime).getTime();
return time/1000; return time/1000;
} }
export function calcDurationByStringTime(startTime, endTime) {
let durationSecond = stringTimeParseToUnix(endTime)-stringTimeParseToUnix(startTime);
let result = `${durationSecond%60}s`;
if (durationSecond > 60) {
result = `${(Math.floor(durationSecond/60))%60}m ${result}`;
}
if (durationSecond > 60*60) {
result = `${Math.floor(durationSecond/(60*60))}h ${result}`;
}
return result;
}
export function unixTimeParseToString(unixTime,fmt='yyyy-MM-dd hh:mm:ss'){ export function unixTimeParseToString(unixTime,fmt='yyyy-MM-dd hh:mm:ss'){
let date=new Date(unixTime * 1000); let date=new Date(unixTime * 1000);
var o = { var o = {
@@ -592,13 +602,8 @@ export const tableSet = {
} }
case 'temrminallog': case 'temrminallog':
switch(prop){ switch(prop){
case 'id': return 'id';
case 'host': return 'host';
case 'port': return 'port';
case 'protocol': return 'protocol'; case 'protocol': return 'protocol';
case 'user': return 'user'; case 'startTime': return 'startTime';
case 'cmd': return 'cmd';
case 'time': return 'time';
default : return prop; default : return prop;
} }
case 'alertRules': case 'alertRules':

View File

@@ -631,6 +631,13 @@ const cn = {
terminallog: { terminallog: {
terminallog: "终端日志", terminallog: "终端日志",
status: "状态", status: "状态",
statusItem: {
connecting: "连接中",
connectionFailed: "连接失败",
over: "已结束",
kickedOut: "被踢出",
unknownError: "未知错误"
},
option: "操作", option: "操作",
host: "主机", host: "主机",
cmd: "命令", cmd: "命令",
@@ -655,7 +662,18 @@ const cn = {
path: "路径", path: "路径",
file: "文件", file: "文件",
success: "成功", success: "成功",
fail: "失败" fail: "失败",
startTime: "开始时间",
duration: "持续",
remote: "远程连接",
replay: "回放",
log: "日志",
record: {
record: "记录",
history: "历史记录",
dangerTip: "这条命令可能是危险的",
legendTip: "图例说明",
}
}, },
dc: { dc: {
dc: "数据中心", dc: "数据中心",

View File

@@ -669,6 +669,13 @@ const en = {
terminallog: { terminallog: {
terminallog: 'Terminal log', terminallog: 'Terminal log',
status: 'Status',//"状态" status: 'Status',//"状态"
statusItem: {
connecting: "Connecting",
connectionFailed: "Connection failed",
over: "Over",
kickedOut: "Kicked out",
unknownError: "Unknown error"
},
option: 'Operation',//"操作", option: 'Operation',//"操作",
host: 'Host', host: 'Host',
cmd: 'CMD', cmd: 'CMD',
@@ -693,7 +700,18 @@ const en = {
path: 'Path', path: 'Path',
file: 'File', file: 'File',
success: 'Success', success: 'Success',
fail: 'Fail' fail: 'Fail',
startTime: "Start time",
duration: "Duration",
remote: "Remote",
replay: "Replay",
log: "Log",
record: {
record: "Record",
history: "History record",
dangerTip: "This CMD may be dangerous",
legendTip: "Legend description",
}
}, },
operationlog: { operationlog: {
operationlog: 'Operation log', operationlog: 'Operation log',

View File

@@ -96,14 +96,6 @@
<span>-</span> <span>-</span>
</template> </template>
</template> </template>
<span v-else-if="item.prop == 'lang'">
{{scope.row[item.prop] == 'en' ? 'English' : ''}}
{{scope.row[item.prop] == 'zh' ? '中文' : ''}}
{{scope.row[item.prop] == 'ru' ? 'русский' : ''}}
</span>
<span v-else-if="item.prop == 'receiver'">
<template v-for="rec in scope.row[item.prop]">{{rec.name}}&nbsp;&nbsp;</template>
</span>
<span v-else-if="item.prop == 'status'"> <span v-else-if="item.prop == 'status'">
<el-switch <el-switch
v-model="scope.row.status" v-model="scope.row.status"

View File

@@ -5,79 +5,110 @@
</style> </style>
<template> <template>
<div class="terminallog"> <div class="terminallog">
<div class="top-tools"> <div :class="{'main-list-with-sub': bottomBox.showSubList}" class="main-list">
<div></div> <!-- 顶部工具栏 -->
<div> <div class="main-modal"></div>
<div class="top-tool-search margin-r-5"> <div class="top-tools" v-show="bottomBox.mainResizeShow">
<search-input :searchMsg="searchMsg" @search="search"></search-input> <div :class="{'top-tool-main-right-to-left': bottomBox.showSubList}" class="top-tool-main-right">
<div class="top-tool-search margin-r-5">
<search-input :searchMsg="searchMsg" @search="search"></search-input>
</div>
</div> </div>
<div class="pagination-top pagination-top-hide display-none"></div>
</div> </div>
</div> <!-- 自定义table列 -->
<!-- 自定义table列 --> <transition name="el-zoom-in-top">
<transition name="el-zoom-in-top"> <element-set
<element-set :custom-table-title.sync="tools.customTableTitle"
id="terminal-log" :original-table-title="tableTitle"
v-if="tools.showCustomTableTitle" @close="tools.showCustomTableTitle = false"
@close="tools.showCustomTableTitle = false" ref="customTableTitle"
:custom-table-title.sync="tools.customTableTitle" v-if="tools.showCustomTableTitle"
:original-table-title="tableTitle" ></element-set>
ref="customTableTitle" </transition>
></element-set> <el-table
</transition> :cell-class-name="messageStyle"
<el-table :data="tableData"
id="terminal-log-table" :height="$tableHeight.normal"
class="nz-table" @sort-change="tableDataSort"
:data="tableData" border
border class="nz-table"
ref="terminalLogTable" ref="terminalLogTable"
:height="$tableHeight.normal" style="width: 100%;"
v-loading="tools.loading" v-loading="tools.loading">
:cell-class-name="messageStyle" <el-table-column
style="width: 100%;" :key="`col-${index}`"
@sort-change="tableDataSort"> :label="item.label"
<el-table-column :prop="$tableSet.propTitle(item.prop,'temrminallog')"
:resizable="true" :resizable="true"
v-for="(item, index) in tools.customTableTitle" :sort-orders="['ascending', 'descending']"
v-if="item.show" :sortable="$tableSet.sortableShow(item.prop,'temrminallog')"
:key="`col-${index}`" v-for="(item, index) in tools.customTableTitle"
:label="item.label" v-if="item.show"
:sortable="$tableSet.sortableShow(item.prop,'temrminallog')" >
:prop="$tableSet.propTitle(item.prop,'temrminallog')" <template :column="item" slot-scope="scope">
:sort-orders="['ascending', 'descending']" <span v-if="item.prop == 'time'">{{utcTimeToTimezoneStr(scope.row[item.prop])}}</span>
> <template v-else-if="item.prop == 'status'">
<template slot-scope="scope" :column="item"> <span>{{getStatusText(scope.row.status)}}</span>
<span v-if="item.prop == 'lang'"> </template>
{{scope.row[item.prop] == 'en' ? 'English' : ''}} <template v-else-if="item.prop == 'remote'">
{{scope.row[item.prop] == 'zh' ? '中文' : ''}} <span>{{getRemoteText(scope.row)}}</span>
{{scope.row[item.prop] == 'ru' ? 'русский' : ''}} </template>
</span> <template v-else-if="item.prop == 'duration'">
<span v-else-if="item.prop == 'time'">{{utcTimeToTimezoneStr(scope.row[item.prop])}}</span> <span>{{getDuration(scope.row)}}</span>
<template v-else-if="item.prop == 'status'"> </template>
<span>{{scope.row.status==='1' ? $t("config.terminallog.success") : $t("config.terminallog.fail")}}</span> <template v-else-if="item.prop == 'option'">
<span :id="'terminalLog-replay-'+scope.row.id" :title="$t('config.terminallog.replay')" @click="showReplay(scope.row)" class="content-right-option"><i class="el-icon-video-play"></i></span>
&nbsp;
<span :id="'terminalLog-log-'+scope.row.id" :title="$t('config.terminallog.log')" @click="showRecord(scope.row)" class="content-right-option"><i class="el-icon-tickets"></i></span>
</template>
<span v-else>{{scope.row[item.prop]}}</span>
</template> </template>
<span v-else>{{scope.row[item.prop]}}</span> </el-table-column>
</template> <el-table-column width="28">
</el-table-column> <template slot="header" slot-scope="scope">
<el-table-column width="28">
<template slot="header" slot-scope="scope">
<span @mousedown.stop="!tools.showCustomTableTitle && (tools.showCustomTableTitle = true)" class="nz-table-gear"> <span @mousedown.stop="!tools.showCustomTableTitle && (tools.showCustomTableTitle = true)" class="nz-table-gear">
<i class="nz-icon nz-icon-gear"></i> <i class="nz-icon nz-icon-gear"></i>
</span> </span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<button :class="{'to-top-is-hover': tools.tableHover}" :style="{top: tools.toTopBtnTop}" @click="toTop(scrollbarWrap)" class="to-top" v-show="tools.showTopBtn" id="terminal-log-totop"><i class="nz-icon nz-icon-top"></i></button> <button :class="{'to-top-is-hover': tools.tableHover}" :style="{top: tools.toTopBtnTop}" @click="toTop(scrollbarWrap)" class="to-top" v-show="tools.showTopBtn"><i class="nz-icon nz-icon-top"></i></button>
<Pagination :tableId="tableId" :pageObj="pageObj" @pageNo='pageNo' @pageSize='pageSize' ref="Pagination"></Pagination> <div class="pagination-bottom" v-show="!bottomBox.showSubList">
<Pagination :pageObj="pageObj" :tableId="tableId" @pageNo='pageNo' @pageSize='pageSize' ref="Pagination"></Pagination>
</div>
</div>
<!-- 底部上滑框 -->
<transition name="el-zoom-in-bottom">
<bottom-box :detail="bottomBox.terminalLog" :is-full-screen="bottomBox.isFullScreen" :obj="bottomBox.terminalLog" :sub-resize-show="bottomBox.subResizeShow" :target-tab.sync="bottomBox.targetTab" @closeSubList="bottomBox.showSubList = false" @exitFullScreen="exitFullScreen"
@fullScreen="fullScreen" @listResize="listResize" from="terminal" v-if="bottomBox.showSubList" ></bottom-box>
</transition>
</div> </div>
</template> </template>
<script> <script>
import bus from "../../../libs/bus"; import bus from "../../../libs/bus";
import {terminalLog} from "../../common/js/constants";
import {calcDurationByStringTime} from "../../common/js/tools";
export default { export default {
name: "terminallog", name: "terminallog",
data() { data() {
return { return {
tableId: 'terminalLogTable', //需要分页的table的id用于记录每页数量 tableId: 'terminalLogTable', //需要分页的table的id用于记录每页数量
/*二级页面相关*/
bottomBox: {
terminalLog: {},
mainResizeShow: true, //dom高度改变时是否展示|隐藏
subResizeShow: true,
isFullScreen: false, //全屏状态
showSubList: false, //是否显示二级列表
targetTab: '', //显示二级列表中的哪个页签
inTransform: false, //搜索框相关搜索条件下拉框是否在transform里
},
/*工具参数*/ /*工具参数*/
tools: { tools: {
loading: false, //是否显示table加载动画 loading: false, //是否显示table加载动画
@@ -108,18 +139,6 @@
isAdd: false, //falsetrueresize isAdd: false, //falsetrueresize
title: '' title: ''
}, },
terminallog: {
id: '',
host: '',
status: '1',
time: '',
protocol: '',
port: '',
user: '',
cmd: '',
authType: '',
userName: ''
},
pageObj: { pageObj: {
pageNo: 1, pageNo: 1,
pageSize: this.$CONSTANTS.defaultPageSize, pageSize: this.$CONSTANTS.defaultPageSize,
@@ -132,12 +151,17 @@
show: true, show: true,
width: 80 width: 80
}, { }, {
label: this.$t('config.terminallog.host'), label: "Session ID",
prop: 'host', prop: 'uuid',
show: true, show: true,
}, { }, {
label: this.$t('config.terminallog.port'), label: "Username",
prop: 'port', prop: 'username',
show: true,
},
{
label: this.$t('config.terminallog.remote'),
prop: 'remote',
show: true, show: true,
}, },
{ {
@@ -146,8 +170,13 @@
show: true, show: true,
}, },
{ {
label: this.$t('config.terminallog.user'), label: this.$t('config.terminallog.startTime'),
prop: 'user', prop: 'startTime',
show: true,
},
{
label: this.$t('config.terminallog.duration'),
prop: 'duration',
show: true, show: true,
}, },
{ {
@@ -156,25 +185,17 @@
show: false show: false
}, },
{ {
label: this.$t('config.terminallog.cmd'), label: this.$t('config.terminallog.status'), // killusername鼠标悬停形式
prop: 'cmd',
show: true,
},
{
label: 'UserName',
prop: 'userName',
show: false,
},
{
label: this.$t('config.terminallog.time'),
prop: 'time',
show: true
},
{
label: this.$t('config.terminallog.status'),
prop: 'status', prop: 'status',
show: true, show: true,
width: 100 width: 100
},
{
label: this.$t('config.account.option'),
prop: 'option',
show: true,
width: 120,
fixed: "right"
} }
], ],
tableData: [], tableData: [],
@@ -200,12 +221,6 @@
type: 'input', type: 'input',
label: 'user', label: 'user',
disabled: false disabled: false
},{
id: 13,
name: this.$t('config.terminallog.cmd'),
type: 'input',
label: 'cmd',
disabled: false
},{ },{
id: 14, id: 14,
name: this.$t('config.terminallog.userId'), name: this.$t('config.terminallog.userId'),
@@ -217,19 +232,72 @@
}, },
searchLabel: {}, //搜索参数 searchLabel: {}, //搜索参数
scrollbarWrap: null, scrollbarWrap: null,
testData: {
code: 200,
data: {
pageNo: 1,
pageSize: 20,
total: 2,
pages: 1,
list: [
{
id: 1,
uuid: "abcdde",
host: "192.168.40.42",
port: 22,
protocol: "SSH",
authType: 1,
username: "admin",
loginUser: "root",
startTime: "2021-02-01 14:20:08",
endTime: "2021-02-01 14:30:08",
status: 3,
killUserName: "leader",
}, {
id: 2,
uuid: "zpppoe",
host: "192.168.40.42",
port: 22,
protocol: "SSH",
authType: 1,
username: "admin",
loginUser: "root",
startTime: "2021-02-01 14:41:08",
endTime: "2021-02-01 14:51:08",
status: 2,
},
]
},
}
} }
}, },
computed: {
getStatusText() {
return function(status) {
return terminalLog.status[status];
}
},
getRemoteText() {
return function(record) {
return `${record.loginUser}@${record.host}:${record.port}`;
}
},
getDuration() {
return function(record) {
if (record.endTime) {
return calcDurationByStringTime(record.startTime, record.endTime);
}
return "";
}
},
},
methods: { methods: {
getTableData: function () { getTableData() {
this.$set(this.searchLabel, "pageNo", this.pageObj.pageNo); this.$get('terminal/session', this.searchLabel).then(response => {
this.$set(this.searchLabel, "pageSize", this.pageObj.pageSize);
this.tools.loading = true;
this.$get('terminal/log', this.searchLabel).then(response => {
this.tools.loading = false; this.tools.loading = false;
//response = this.testData;
if (response.code === 200) { if (response.code === 200) {
for (let i = 0; i < response.data.list.length; i++) {
response.data.list[i].status = response.data.list[i].status + "";
}
this.tableData = response.data.list; this.tableData = response.data.list;
this.pageObj.total = response.data.total; this.pageObj.total = response.data.total;
if (!this.scrollbarWrap) { if (!this.scrollbarWrap) {
@@ -241,11 +309,62 @@
} }
}) })
}, },
/*getTableData: function () {
this.$set(this.searchLabel, "pageNo", this.pageObj.pageNo);
this.$set(this.searchLabel, "pageSize", this.pageObj.pageSize);
this.tools.loading = true;
this.$get('terminal/log', this.searchLabel).then(response => {
this.tools.loading = false;
response = this.testData;
if (response.code === 200) {
this.tableData = response.data.list;
this.pageObj.total = response.data.total;
if (!this.scrollbarWrap) {
this.$nextTick(() => {
this.scrollbarWrap = this.$refs.terminalLogTable.bodyWrapper;
this.toTopBtnHandler(this.scrollbarWrap);
});
}
}
})
},*/
showReplay(record) {
this.bottomBox.targetTab = 'replay';
this.bottomBox.terminalLog = JSON.parse(JSON.stringify(record));
this.bottomBox.showSubList = true;
},
showRecord(record) {
this.bottomBox.targetTab = 'record';
this.bottomBox.terminalLog = JSON.parse(JSON.stringify(record));
this.bottomBox.showSubList = true;
},
// 全屏
fullScreen() {
let vm = this;
this.$bottomBoxWindow.fullScreen(vm);
},
// 退出全屏
exitFullScreen() {
let vm = this;
this.$bottomBoxWindow.exitFullScreen(vm);
},
// 鼠标拖动二级列表
listResize(e) {
let vm = this;
this.$bottomBoxWindow.listResize(vm, e);
},
messageStyle(e) { messageStyle(e) {
if (e.column.label == this.$t('config.terminallog.status')) { if (e.column.label == this.$t('config.terminallog.status')) {
if (e.row.status == '1') { if (e.row.status == '0') {
return 'success'; return 'success';
} else { } else if (e.row.status == '1') {
return 'warning';
} else if (e.row.status == '2') {
return 'suspended';
} else if (e.row.status == '3') {
return 'danger';
} else if (e.row.status == '4') {
return 'danger'; return 'danger';
} }
} }