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

@@ -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>