CN-730 feat:出入链路统计及下一跳开发
This commit is contained in:
@@ -66,7 +66,83 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$blue: #046ECA;
|
||||
.item-popover-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
|
||||
.item-popover-header-icon {
|
||||
font-size: 20px;
|
||||
margin: 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.item-popover-block {
|
||||
.item-popover-block-title {
|
||||
line-height: 24px;
|
||||
font-size: 13px;
|
||||
color: $blue;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.item-popover-block-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.block-content-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.block-content-item-name {
|
||||
}
|
||||
|
||||
.block-content-item-value {
|
||||
display: flex;
|
||||
min-width: 95px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row-dot {
|
||||
margin-top: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.green-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: #749F4D;
|
||||
}
|
||||
|
||||
.red-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: #E26154;
|
||||
}
|
||||
|
||||
.item-popover-up, .item-popover-down {
|
||||
font-size: 17px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.data-item__hover:hover {
|
||||
transition: all 0.2s;
|
||||
background: #F4F9FD;
|
||||
border: 2px $blue solid !important;
|
||||
box-shadow: 1px 1px 5px #a1a1a1 !important;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ if (openMock) {
|
||||
const egressLinkIds = ['257', '513', '769', '1025', '1281', '1537', '1793', '2049', '2305', '2817']
|
||||
ingressLinkIds.forEach(ingress => {
|
||||
egressLinkIds.forEach(egress => {
|
||||
data.push({ egressLinkId: egress, ingressLinkId: ingress, egressUsage: 128000, ingressUsage: 12800, totalBitsRate: 985412, score: 6, tcpConnectionEstablishLatency: 50, httpResponseLatency: 50, sslResponseLatency: 50, packetsLoss: 0.2, packetRetrans: 0.1 })
|
||||
data.push({ egressLinkId: egress, ingressLinkId: ingress, egressBytes: 12800000000, ingressBytes: 52800000000, totalBytes: 98541200, score: 6, tcpConnectionEstablishLatency: 50, httpResponseLatency: 50, sslResponseLatency: 50, packetsLoss: 0.2, packetRetrans: 0.1 })
|
||||
})
|
||||
})
|
||||
return {
|
||||
@@ -110,4 +110,40 @@ if (openMock) {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Mock.mock(new RegExp(BASE_CONFIG.baseUrl + 'interface/linkMonitor/bigramAnalysis1.*'), 'get', function (requestObj) {
|
||||
const data = []
|
||||
const ingressLinkIds = ['256', '512', '768', '1024', '1280', '1536', '1792', '2048', '2304', '2816']
|
||||
const egressLinkIds = ['257', '513', '769', '1025', '1281', '1537', '1793', '2049', '2305', '2817']
|
||||
ingressLinkIds.forEach(ingress => {
|
||||
egressLinkIds.forEach(egress => {
|
||||
data.push({ egressLinkId: egress, ingressLinkId: ingress, egressBytes: 12800000000, ingressBytes: 52800000000, totalBytes: 98541200, establishLatencyMs: 50, httpResponseLatency: 50, sslConLatency: 50, tcpLostlenPercent: 0.2, pktRetransPercent: 0.1 })
|
||||
})
|
||||
})
|
||||
return {
|
||||
msg: 'success',
|
||||
code: 200,
|
||||
data: {
|
||||
result: data
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Mock.mock(new RegExp(BASE_CONFIG.baseUrl + 'interface/linkMonitor/bigramNextHopAnalysis1.*'), 'get', function (requestObj) {
|
||||
const data = []
|
||||
const ingressLinkIds = ['西安', '太原', '西宁']
|
||||
const egressLinkIds = ['西安', '太原', '西宁']
|
||||
ingressLinkIds.forEach(ingress => {
|
||||
egressLinkIds.forEach(egress => {
|
||||
data.push({ egressLinkDirection: egress, ingressLinkDirection: ingress, egressBytes: 12800000000, ingressBytes: 52800000000, totalBytes: 985412000, establishLatencyMs: 50, httpResponseLatency: 50, sslConLatency: 50, tcpLostlenPercent: 0.2, pktRetransPercent: 0.1 })
|
||||
})
|
||||
})
|
||||
return {
|
||||
msg: 'success',
|
||||
code: 200,
|
||||
data: {
|
||||
result: data
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -203,7 +203,11 @@ export const api = {
|
||||
networkAnalysis: '/interface/link/overview/drilldown/networkAnalysis',
|
||||
linkTrafficDirection: '/interface/linkMonitor/linkTrafficDirection',
|
||||
quadrupleIngressAnalysis: '/interface/link/overview/quadrupleIngressAnalysis', // 入口
|
||||
quadrupleEgressAnalysis: '/interface/link/overview/quadrupleEgressAnalysis' // 出口
|
||||
quadrupleEgressAnalysis: '/interface/link/overview/quadrupleEgressAnalysis', // 出口
|
||||
bigramAnalysis: '/interface/link/overview/bigramAnalysis',
|
||||
bigramNextHopAnalysis: '/interface/link/overview/bigramNextHopAnalysis',
|
||||
bigramAnalysis1: 'interface/linkMonitor/bigramAnalysis1',
|
||||
bigramNextHopAnalysis1: '/interface/linkMonitor/bigramNextHopAnalysis1'
|
||||
},
|
||||
dnsInsight: {
|
||||
recentEvents: '/interface/dnsInsight/recentEvents',
|
||||
|
||||
@@ -1,41 +1,10 @@
|
||||
<template>
|
||||
<div class="link-direction-grid">
|
||||
<div class="link-statistical-dimension" style="width: 900px;">
|
||||
<div class="dimension-title">{{$t('linkMonitor.egressLink')}} & {{$t('linkMonitor.ingressLink')}}</div>
|
||||
<div class="data-grid">
|
||||
<div class="egress-row">
|
||||
<div class="egress-id" v-for="(item, index) in gridData" :key="index">{{item.linkId}}</div>
|
||||
</div>
|
||||
<div class="data-row" v-for="(row, index) in gridData" :key="index">
|
||||
<div class="ingress-id">{{row.linkId}}</div>
|
||||
<div class="data-item" v-for="(item, index2) in row.egress" :key="index2">
|
||||
<div class="data-item__point"></div>
|
||||
<div class="data-item__point"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="link-statistical-dimension">
|
||||
<div class="dimension-title">{{$t('linkMonitor.egressLink')}} & {{$t('linkMonitor.ingressLink')}}</div>
|
||||
<div class="data-grid">
|
||||
<div class="egress-row">
|
||||
<template v-for="(item, index) in gridData">
|
||||
<div class="egress-id" v-if="index <= 2" :key="index">{{item.linkId}}</div>
|
||||
</template>
|
||||
</div>
|
||||
<template v-for="(row, index) in gridData">
|
||||
<div class="data-row" v-if="index <= 2" :key="index">
|
||||
<div class="ingress-id">{{row.linkId}}</div>
|
||||
<template v-for="(row, index2) in row.egress">
|
||||
<div class="data-item" v-if="index2 <= 2" :key="index2">
|
||||
<div class="data-item__point"></div>
|
||||
<div class="data-item__point"></div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!--左侧链路出入口-->
|
||||
<popover-content :gridData="gridData" style="width: 900px"/>
|
||||
|
||||
<!--右侧链路下一跳-->
|
||||
<popover-content :gridData="gridData2"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -44,6 +13,9 @@ import chartMixin from '@/views/charts2/chart-mixin'
|
||||
import { getSecond } from '@/utils/date-util'
|
||||
import { api } from '@/utils/api'
|
||||
import { get } from '@/utils/http'
|
||||
import { storageKey } from '@/utils/constants'
|
||||
import PopoverContent from './LinkDirectionGrid/PopoverContent'
|
||||
import { computeScore } from '@/utils/tools'
|
||||
|
||||
export default {
|
||||
name: 'LinkDirectionGrid',
|
||||
@@ -54,46 +26,168 @@ export default {
|
||||
gridData2: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init () {
|
||||
const params = {
|
||||
startTime: getSecond(this.timeFilter.startTime),
|
||||
endTime: getSecond(this.timeFilter.endTime)
|
||||
components: {
|
||||
PopoverContent
|
||||
},
|
||||
watch: {
|
||||
timeFilter: {
|
||||
deep: true,
|
||||
handler (n) {
|
||||
this.init()
|
||||
}
|
||||
const configRequest = get(api.config, { ckey: 'link_info' })
|
||||
const dataRequest = get(api.linkMonitor.linkTrafficDirection, params)
|
||||
Promise.all([configRequest, dataRequest]).then(res => {
|
||||
if (res[0].code === 200 && res[1].code === 200) {
|
||||
if (res[0].page.list && res[0].page.list.length > 0) {
|
||||
// 链路基本信息
|
||||
const linkInfo = JSON.parse(res[0].page.list[0].cvalue)
|
||||
// 链路流量数据
|
||||
const linkData = res[1].data.result
|
||||
const gridData = []
|
||||
linkData.forEach(d => {
|
||||
const ingressLink = linkInfo.find(l => l.originalLinkId === d.ingressLinkId)
|
||||
const egressLink = linkInfo.find(l => l.originalLinkId === d.egressLinkId)
|
||||
if (ingressLink && egressLink) {
|
||||
const data = gridData.find(g => g.linkId === ingressLink.linkId)
|
||||
if (data) {
|
||||
const existedEgressLink = data.egress.find(e => e.linkId === egressLink.linkId)
|
||||
if (!existedEgressLink) {
|
||||
data.egress.push({ linkId: egressLink.linkId, totalBytes: d.totalBytes })
|
||||
}
|
||||
} else {
|
||||
gridData.push({ linkId: ingressLink.linkId, egress: [{ linkId: egressLink.linkId, totalBytes: d.totalBytes }] })
|
||||
}
|
||||
}
|
||||
})
|
||||
this.gridData = gridData
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.toggleLoading(false)
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
init () {
|
||||
// 链路基本信息
|
||||
let linkInfo = localStorage.getItem(storageKey.linkInfo)
|
||||
linkInfo = JSON.parse(linkInfo)
|
||||
console.log('LinkDirectionGrid.vue---init--获取链路基本信息缓存', linkInfo)
|
||||
|
||||
const params = {
|
||||
startTime: getSecond(this.timeFilter.startTime),
|
||||
endTime: getSecond(this.timeFilter.endTime)
|
||||
}
|
||||
|
||||
const dataRequest = get(api.linkMonitor.bigramAnalysis1, params)
|
||||
const nextHopRequest = get(api.linkMonitor.bigramNextHopAnalysis1, params)
|
||||
|
||||
Promise.all([dataRequest, nextHopRequest]).then(res => {
|
||||
if (res[0].code === 200 && res[1].code === 200) {
|
||||
// 链路流量数据
|
||||
const linkData = res[0].data.result
|
||||
// 链路下一跳信息
|
||||
const nextLinkData = res[1].data.result
|
||||
|
||||
// 链路流量数据
|
||||
const gridData = []
|
||||
// 链路下一跳数据
|
||||
const gridData2 = []
|
||||
linkData.forEach(d => {
|
||||
const ingressLink = linkInfo.find(l => l.originalLinkId === d.ingressLinkId)
|
||||
const egressLink = linkInfo.find(l => l.originalLinkId === d.egressLinkId)
|
||||
if (ingressLink && egressLink) {
|
||||
const data = gridData.find(g => g.linkId === ingressLink.linkId)
|
||||
|
||||
// 上行使用情况计算
|
||||
const egressUsage = this.computeUsage(d.egressBytes, egressLink.bandwidth)
|
||||
// 下行使用情况计算
|
||||
const ingressUsage = this.computeUsage(d.ingressBytes, egressLink.bandwidth)
|
||||
// 计算npm分数
|
||||
d.score = this.localComputeScore(d)
|
||||
|
||||
if (data) {
|
||||
const existedEgressLink = data.egress.find(e => e.linkId === egressLink.linkId)
|
||||
if (!existedEgressLink) {
|
||||
data.egress.push({
|
||||
linkId: egressLink.linkId,
|
||||
egressUsage: egressUsage,
|
||||
ingressUsage: ingressUsage,
|
||||
totalBytes: d.totalBytes,
|
||||
...d
|
||||
})
|
||||
}
|
||||
} else {
|
||||
gridData.push({
|
||||
linkId: ingressLink.linkId,
|
||||
egress: [{
|
||||
linkId: egressLink.linkId,
|
||||
egressUsage: egressUsage,
|
||||
ingressUsage: ingressUsage,
|
||||
totalBytes: d.totalBytes,
|
||||
...d
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
nextLinkData.forEach(d => {
|
||||
const ingressLink = linkInfo.find(l => l.nextHop === d.ingressLinkDirection && l.direction === 'ingress')
|
||||
const egressLink = linkInfo.find(l => l.nextHop === d.egressLinkDirection && l.direction === 'egress')
|
||||
|
||||
if (ingressLink && egressLink) {
|
||||
const data = gridData2.find(g => g.linkId === ingressLink.linkId)
|
||||
|
||||
let egressBanwidth = 0
|
||||
let ingressBanwidth = 0
|
||||
linkInfo.forEach((item) => {
|
||||
if (item.nextHop === d.egressLinkDirection && item.direction === 'egress') {
|
||||
egressBanwidth += item.bandwidth
|
||||
}
|
||||
if (item.nextHop === d.ingressLinkDirection && item.direction === 'ingress') {
|
||||
ingressBanwidth += item.bandwidth
|
||||
}
|
||||
})
|
||||
|
||||
// 上行使用情况计算
|
||||
const egressUsage = this.computeUsage(d.egressBytes, egressBanwidth)
|
||||
// 下行使用情况计算
|
||||
const ingressUsage = this.computeUsage(d.ingressBytes, ingressBanwidth)
|
||||
// 计算npm分数
|
||||
d.score = this.localComputeScore(d)
|
||||
|
||||
if (data) {
|
||||
const existedEgressLink = data.egress.find(e => e.linkId === egressLink.linkId)
|
||||
if (!existedEgressLink) {
|
||||
data.egress.push({
|
||||
linkId: egressLink.linkId,
|
||||
nextHop: egressLink.nextHop,
|
||||
egressUsage: egressUsage,
|
||||
ingressUsage: ingressUsage,
|
||||
totalBytes: d.totalBytes,
|
||||
...d
|
||||
})
|
||||
}
|
||||
} else {
|
||||
gridData2.push({
|
||||
linkId: ingressLink.linkId,
|
||||
nextHop: ingressLink.nextHop,
|
||||
egress: [{
|
||||
linkId: egressLink.linkId,
|
||||
nextHop: ingressLink.nextHop,
|
||||
egressUsage: egressUsage,
|
||||
ingressUsage: ingressUsage,
|
||||
totalBytes: d.totalBytes,
|
||||
...d
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log('左侧出入链路数据', gridData)
|
||||
console.log('右侧下一跳数据', gridData2)
|
||||
this.gridData = gridData
|
||||
this.gridData2 = gridData2
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 计算上下行使用占比
|
||||
*/
|
||||
computeUsage (e, bandwidth) {
|
||||
return Math.ceil((e / bandwidth) * 100) + '%'
|
||||
},
|
||||
/**
|
||||
* 本地计算npm分数
|
||||
*/
|
||||
localComputeScore (data, bandwidth) {
|
||||
const keyPre = ['tcp', 'http', 'ssl', 'tcpLost', 'packetRetrans']
|
||||
let score = 0
|
||||
keyPre.forEach((item, index) => {
|
||||
score = computeScore(data, index)
|
||||
data[keyPre[index] + 'Score'] = score
|
||||
})
|
||||
let npmScore = Math.ceil((data.tcpScore + data.httpScore + data.sslScore + data.tcpLostScore + data.packetRetransScore) * 6)
|
||||
if (npmScore > 6) {
|
||||
npmScore = 6
|
||||
}
|
||||
return npmScore
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="link-statistical-dimension">
|
||||
<div class="dimension-title">{{ $t('linkMonitor.egressLink') }} & {{ $t('linkMonitor.ingressLink') }}
|
||||
</div>
|
||||
<div class="data-grid">
|
||||
<div class="egress-row">
|
||||
<div class="egress-id" v-for="(item, index) in gridData" :key="index">
|
||||
<!--兼容下一跳情况-->
|
||||
<span v-if="item.nextHop">{{ item.nextHop }}</span>
|
||||
<span v-else>{{ item.linkId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" v-for="(row, index2) in gridData" :key="index2">
|
||||
<div class="ingress-id">
|
||||
<span v-if="row.nextHop">{{ row.nextHop }}</span>
|
||||
<span v-else>{{ row.linkId }}</span>
|
||||
</div>
|
||||
<div v-for="(item, index2) in row.egress" :key="index2">
|
||||
|
||||
<el-popover :width="370" placement="right" trigger="hover">
|
||||
<template #reference>
|
||||
<div class="data-item data-item__hover">
|
||||
<div class="data-item__point"></div>
|
||||
<div class="data-item__point"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!--鼠标移动的弹窗模块-->
|
||||
<template #default>
|
||||
<div class="item-popover-header">
|
||||
<span v-if="row.nextHop">{{ row.nextHop }}</span>
|
||||
<span v-else>{{ row.linkId }}</span>
|
||||
<svg class="icon item-popover-header-icon" aria-hidden="true">
|
||||
<use xlink:href="#cn-icon-arrow-right2"></use>
|
||||
</svg>
|
||||
<span v-if="row.nextHop">{{ row.egress[index2].nextHop }}</span>
|
||||
<span v-else>{{ row.linkId }}</span>
|
||||
</div>
|
||||
|
||||
<div class="item-popover-block">
|
||||
<div class="item-popover-block-title">Traffic</div>
|
||||
|
||||
<div style="display: flex">
|
||||
<div class="row-dot">
|
||||
<div class="green-dot"></div>
|
||||
</div>
|
||||
<div class="item-popover-block-content">
|
||||
<div class="block-content-item">
|
||||
<div class="block-content-item-name">Bandwidth Usage</div>
|
||||
|
||||
<div class="block-content-item-value">
|
||||
<div>
|
||||
<svg class="icon item-popover-up" aria-hidden="true">
|
||||
<use xlink:href="#cn-icon-egress"></use>
|
||||
</svg>
|
||||
{{ row.egress[index2].egressUsage }}
|
||||
</div>
|
||||
<div>
|
||||
<svg class="icon item-popover-down" aria-hidden="true">
|
||||
<use xlink:href="#cn-icon-ingress"></use>
|
||||
</svg>
|
||||
{{ row.egress[index2].ingressUsage }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-content-item">
|
||||
<div class="block-content-item-name">Total</div>
|
||||
|
||||
<div class="block-content-item-value">
|
||||
{{ row.egress[index2].totalBytes / 1000 }}Mbps
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-popover-block">
|
||||
<div class="item-popover-block-title">Performance</div>
|
||||
|
||||
<div style="display: flex">
|
||||
<div class="row-dot">
|
||||
<div class="green-dot"></div>
|
||||
</div>
|
||||
<div class="item-popover-block-content">
|
||||
<div class="block-content-item">
|
||||
<div class="block-content-item-name">Score</div>
|
||||
<div class="block-content-item-value">
|
||||
{{ row.egress[index2].score }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-content-item">
|
||||
<div class="block-content-item-name">TCP connection establish latency</div>
|
||||
<div class="block-content-item-value">
|
||||
{{ row.egress[index2].establishLatencyMs }}ms
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-content-item">
|
||||
<div class="block-content-item-name">HTTP response latency</div>
|
||||
<div class="block-content-item-value">
|
||||
{{ row.egress[index2].httpResponseLatency }}ms
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-content-item">
|
||||
<div class="block-content-item-name">SSL response latency</div>
|
||||
<div class="block-content-item-value">
|
||||
{{ row.egress[index2].sslConLatency }}ms
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-content-item">
|
||||
<div class="block-content-item-name">Packet loss</div>
|
||||
<div class="block-content-item-value">
|
||||
{{ row.egress[index2].tcpLostlenPercent * 100 }}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-content-item">
|
||||
<div class="block-content-item-name">Packet retrains</div>
|
||||
<div class="block-content-item-value">
|
||||
{{ row.egress[index2].pktRetransPercent * 100 }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PopoverContent',
|
||||
props: {
|
||||
gridData: Array
|
||||
},
|
||||
created () {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user