feat: terminal日志回放,节前提交一半关机没提成功
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user