This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
nezha-nezha-fronted/nezha-fronted/src/components/page/tool/trace.vue

388 lines
14 KiB
Vue
Raw Normal View History

2022-04-11 11:12:30 +08:00
<template>
<nz-data-list
ref="dataList"
:layout="[]"
:from="fromRoute.trace"
class="radar-box"
2022-04-11 11:12:30 +08:00
>
<template v-slot:top-tool-left>
<div class="tools-header">
<div class="tools-header-left">
<span>{{$t('config.operationlog.ip')}}</span>
<vue-tags-input
class="ipInput"
v-model="ip"
:placeholder="$t('overall.placeHolder')+' IP'"
:add-from-paste="false"
:tags="tags"
@tags-changed="newTags => tags = newTags"
@before-adding-tag="value => beforeAddTag(value)"
@adding-duplicate="value => addDuplicate(value)"
/>
</div>
<div class="tools-header-center">
<el-form size="small" ref="ruleForm" :model="ruleForm" :rules="formRules" label-position="right" label-width="150px">
2022-06-21 14:17:46 +08:00
<el-form-item style="margin-bottom:10px" :label="$t('config.dc.dc')">
2022-05-27 14:01:57 +08:00
<!-- <el-select v-model="checked" multiple @change="checkedChange" >-->
<!-- <el-option-->
<!-- :key="item.id"-->
<!-- v-for="item in dataCenter"-->
<!-- :label="item.name"-->
<!-- :value="item.id">-->
<!-- </el-option>-->
<!-- </el-select>-->
<el-popover
placement="bottom"
width="220"
trigger="manual"
v-model="visible"
v-clickoutside="close"
popper-class="no-style-class ping-popover"
>
<el-form-item style="margin-bottom:0px">
<ul class="pop-list-wrap">
<li class="el-dropdown-menu__item">
<el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="checkAllChange">{{$t('overall.all')}}</el-checkbox>
</li>
<ul class="pop-list">
<el-checkbox-group v-model="checked" @change="checkedChange">
<li class="el-dropdown-menu__item" v-for="item in dataCenter" :key="item.id">
<el-checkbox :label="item.id">{{item.name}}</el-checkbox>
</li>
</el-checkbox-group>
</ul>
</ul>
</el-form-item>
<div slot="reference" class="choose-box" @click="triggerVisible">
<div v-if="checkAll" style="flex: 1">All</div>
<div v-else style="width: 100%;flex: 1">
<span class="choose" v-for="item in dataCenter" :key="item.id" v-if="checked.indexOf(item.id) !== -1">
<span> {{item.name}} <i class="nz-icon nz-icon-close" @click="removeCheckedItem(item.id)"/></span>
</span>
</div>
<i class="nz-icon nz-icon-arrow-down6"/>
</div>
</el-popover>
</el-form-item>
<el-form-item prop="maxHops" style="margin-bottom:10px" :label="$t('trace.maxHops')">
<div class="wrap" style="height:32px">
<el-input v-model.number="ruleForm.maxHops" placeholder=""></el-input>
</div>
</el-form-item>
<el-form-item prop="timeout" style="margin-bottom:10px" :label="$t('ping.timeout')">
<div class="wrap" style="height:32px">
<el-input v-model.number="ruleForm.timeout" placeholder="" >
<template slot="append">{{$t('alert.config.second')}}</template>
</el-input>
</div>
</el-form-item>
</el-form>
</div>
<div class="tools-header-right">
<div class="radar">
<div class="first-circle"></div>
<div class="second-circle"></div>
<div class="third-circle"></div>
<div class="sector" :class="isStart?'active':''"></div>
<div class="radar-line"></div>
2022-04-11 11:12:30 +08:00
</div>
<div class="tools-header-right-content">
<div class="tools-header-right-title right-content-text">{{$t('dashboard.panel.chartForm.statistics')}}</div>
<div class="right-content-text right-content-box" >
<span class="margin-l-5">{{$t('asset.total')}}:<span class="margin-l-10 margin-r-30">{{total}}</span></span>
<span>{{$t('ping.done')}}:<span class="margin-l-10 margin-r-30">{{done}}</span></span>
<span>{{$t('ping.progress')}}:<span class="margin-l-10 margin-r-30">{{process}}%</span></span>
</div>
<el-button class="nz-btn nz-btn-size-normal nz-btn-style-normal" v-if="!isStart" @click="startTask">
{{$t('ping.trace')}}
</el-button>
<el-button class="nz-btn nz-btn-size-normal nz-btn-style-normal" v-else @click="clearTask">
{{$t('config.terminallog.stop')}}
</el-button>
2022-04-11 11:12:30 +08:00
</div>
</div>
<div class="tools-header-title">{{$t('project.module.configs')}}</div>
</div>
</template>
2022-04-11 11:12:30 +08:00
<template v-slot:default>
<!-- 初始展示的内容 ip输入框聚焦后消失 -->
<div class="empty" v-if="tid===undefined">
<el-steps align-center>
<el-step>
<span class="nz-icon nz-icon-edit" slot="icon"></span>
<p class="txt" slot="title">{{$t('overall.placeHolder')}}IP</p>
2022-04-11 11:12:30 +08:00
</el-step>
<el-step>
<span class="msg" slot="icon">Trace</span>
<p class="txt" slot="title">Trace {{$t('config.terminallog.cmd.cmd')}}</p>
</el-step>
</el-steps>
</div>
<!-- 存在任务id时展示表格 -->
<div class="data-wrap" v-show="tid!==undefined">
<div class="data-bottom">
<trace-table
ref="dataTable"
:loading="loading"
v-my-loading="loading"
:custom-table-title="tools.customTableTitle"
:height="mainTableHeight"
:table-data="tableData"
>
</trace-table>
</div>
</div>
</template>
</nz-data-list>
</template>
<script>
import bus from '@/libs/bus'
2022-04-11 11:12:30 +08:00
import nzDataList from '@/components/common/table/nzDataList'
import dataListMixin from '@/components/common/mixin/dataList'
import traceTable from '@/components/common/table/tool/traceTable'
import VueTagsInput from '@johmun/vue-tags-input'
2022-04-11 11:12:30 +08:00
import { positiveInteger } from '../../common/js/validate'
const ipv4 = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])(\:\d{0,5})?$/
const ipv6 = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/
2022-04-11 11:12:30 +08:00
export default {
mixins: [dataListMixin],
components: {
nzDataList,
traceTable,
VueTagsInput
2022-04-11 11:12:30 +08:00
},
data () {
return {
// 防止用户重复点击
flag: true,
loading: false,
// 弹出框是否显示
visible: false,
ip: '',
tags: [],
2022-04-11 11:12:30 +08:00
// 是否全选
checkAll: true,
isIndeterminate: false,
// 数据中心
dataCenter: [],
// 复选框选中的值
checked: [],
// 是否正在请求数据
isStart: false,
// 定时器id
timer: null,
// 任务id
tid: undefined,
// 表格总数据
total: 0,
// 已完成
done: 0,
// 进度
process: 0,
// 表格数据
tableData: [],
ruleForm: {
// 最大跃点数
maxHops: '',
// 超时时间
2022-05-27 14:01:57 +08:00
timeout: 30
2022-04-11 11:12:30 +08:00
},
formRules: {
timeout: [{ validator: positiveInteger, trigger: 'blur' }],
maxHops: [{ validator: positiveInteger, trigger: 'blur' }]
}
}
},
created () {
this.getDataCenter()
},
2022-05-27 14:01:57 +08:00
mounted () {
const tiInput = document.getElementsByClassName('ti-input')[0]
tiInput.addEventListener('click', (e) => {
const event = e || window.event
if (event && event.stopPropagation) {
event.stopPropagation()
} else {
event.cancelBubble = true
}
if (event.path[0].className === 'ti-input') {
const tiInputBox = document.getElementsByClassName('ti-new-tag-input')[0]
tiInputBox.focus()
}
})
},
2022-04-11 11:12:30 +08:00
methods: {
// 使用防抖是因为,防止标签输入框失去焦点校验和开始任务校验重复(连续两次message提示)
validateHost: bus.debounce(function () {
this.$message.error(this.$t('validate.host'))
},
50),
// 使用防抖是因为,防止标签输入框失去焦点校验和开始任务校验重复(连续两次message提示)
validateDuplicate: bus.debounce(function () {
this.$message.error(this.$t('ping.duplicate') + ' IP')
},
50),
// 添加标签之前
beforeAddTag ({ tag, addTag }) {
if (!ipv4.test(tag.text) && !ipv6.test(tag.text)) {
return this.validateHost()
}
addTag()
},
// 添加重复的标签
addDuplicate () {
return this.validateDuplicate()
},
2022-04-11 11:12:30 +08:00
close () {
this.$refs.ruleForm.validate((valid) => {
if (valid) {
this.visible = false
}
})
},
// 切换弹框显示隐藏
triggerVisible () {
if (!this.visible) {
this.visible = true
} else {
this.close()
}
},
// 点击全选
checkAllChange (value) {
const allId = this.dataCenter.map(item => {
return item.id
})
this.checked = value ? allId : []
this.isIndeterminate = false
},
// 点击单个选中
checkedChange (value) {
const checkedCount = value.length
this.checkAll = checkedCount === this.dataCenter.length
this.isIndeterminate = checkedCount > 0 && checkedCount < this.dataCenter.length
},
2022-05-27 14:01:57 +08:00
// 删除选中
removeCheckedItem (id, e) {
const event = e || window.event
if (event && event.stopPropagation) {
event.stopPropagation()
} else {
event.cancelBubble = true
}
this.checked = this.checked.filter(dcId => dcId !== id)
},
2022-04-11 11:12:30 +08:00
// 开始任务
startTask () {
if (!this.flag) {
return false
}
setTimeout(() => {
if (this.ip) {
// 校验ip格式
if (!ipv4.test(this.ip) && !ipv6.test(this.ip)) {
return this.validateHost()
}
// 判断ip是否重复
if (this.tags.some(item => item.text === this.ip)) {
return this.validateDuplicate()
}
} else if (!this.tags.length) {
return this.validateHost()
}
this.flag = false
this.getId()
})
2022-04-11 11:12:30 +08:00
},
// 请求dataCenter
getDataCenter () {
this.$get('/dc?pageSize=-1').then(response => {
this.dataCenter = response.data.list
// 默认全选
this.checked = this.dataCenter.map(item => {
return item.id
})
})
},
// 请求任务id
async getId () {
this.loading = true
const ipArr = this.tags.map(item => item.text)
2022-04-11 11:12:30 +08:00
const params = {
ips: ipArr.join(','),
2022-04-11 11:12:30 +08:00
dcIds: this.checked.join(','),
maxHops: this.ruleForm.maxHops,
timeout: this.ruleForm.timeout
}
this.$get('/tool/traceroute', params).then(response => {
// 清空上一次任务的数据
this.done = 0
this.total = 0
this.process = 0
this.tableData = []
this.tid = response.data.tid
this.isStart = true // 标记正在请求数据中
this.flag = true
this.timer = setInterval(() => {
if (parseInt(this.process) < 100) {
this.getData()
} else {
this.clearTask()
}
}, 300)
// 如果没有数据loading取消
if (!response.data.task.total) {
this.loading = false
}
})
},
// 请求表格数据
getData () {
const currentId = this.tid
this.$get('/tool/traceroute/result/' + this.tid).then(response => {
if (currentId === this.tid && parseInt(this.process) < 100) {
this.done = response.data.task.done
this.total = response.data.task.total
this.process = response.data.task.process
this.tableData.push(...response.data.list)
// 收到数据loading取消
if (this.tableData.length) {
this.loading = false
}
}
})
},
// 清除任务
async clearTask () {
if (!this.flag) {
return false
}
this.flag = false
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
await this.$get('/tool/traceroute/cancel/' + this.tid)
this.isStart = false
this.flag = true
this.loading = false
},
// 空函数 防止mixins中的函数执行
getTableData () { }
},
// 离开页面的时候触发
async beforeRouteLeave (to, from, next) {
if (this.tid) {
await this.clearTask()
}
next()
}
}
</script>
<style lang="scss" scoped>
</style>