141 lines
4.5 KiB
Vue
141 lines
4.5 KiB
Vue
<script setup lang="ts">
|
||
import { LineChart } from 'echarts/charts'
|
||
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
|
||
import { use } from 'echarts/core'
|
||
import { CanvasRenderer } from 'echarts/renderers'
|
||
import { NSelect, NSpin } from 'naive-ui'
|
||
import { computed, ref, watch } from 'vue'
|
||
import VChart from 'vue-echarts'
|
||
|
||
import { getInstanceTraffic } from '@/api/analytics'
|
||
import EmptyCard from '@/components/EmptyCard.vue'
|
||
import FootNote from '@/components/FootNote.vue'
|
||
import { fmtBytes } from '@/composables/useFormat'
|
||
import { useAsync } from '@/composables/useAsync'
|
||
import { useAppStore } from '@/stores/app'
|
||
import { darkTokens, tokens } from '@/theme/tokens'
|
||
|
||
use([LineChart, GridComponent, TooltipComponent, LegendComponent, CanvasRenderer])
|
||
|
||
/** echarts 不认 CSS 变量,色板按明暗主题从 tokens 取 */
|
||
const app = useAppStore()
|
||
const t = computed(() => (app.dark ? darkTokens : tokens))
|
||
|
||
const props = defineProps<{ cfgId: number; instanceId: string; region?: string }>()
|
||
|
||
const days = ref(7)
|
||
|
||
const traffic = useAsync(() =>
|
||
getInstanceTraffic(props.cfgId, props.instanceId, days.value, props.region),
|
||
)
|
||
|
||
watch(days, () => void traffic.run())
|
||
|
||
const daysOptions = [
|
||
{ label: '近 7 天', value: 7 },
|
||
{ label: '近 30 天', value: 30 },
|
||
]
|
||
|
||
const series = computed(() => {
|
||
const vnic = traffic.data.value?.vnics[0]
|
||
if (!vnic) return null
|
||
const labels = vnic.outbound.map((p) => p.timestamp.slice(5, 10))
|
||
return {
|
||
labels,
|
||
outbound: vnic.outbound.map((p) => +(p.bytes / 1024 ** 3).toFixed(2)),
|
||
inbound: vnic.inbound.map((p) => +(p.bytes / 1024 ** 3).toFixed(2)),
|
||
}
|
||
})
|
||
|
||
const option = computed(() => ({
|
||
grid: { left: 44, right: 16, top: 36, bottom: 26 },
|
||
legend: { top: 4, left: 8, textStyle: { color: t.value.ink2, fontSize: 12 }, itemWidth: 14 },
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
backgroundColor: t.value.ink,
|
||
borderWidth: 0,
|
||
textStyle: { color: t.value.bg, fontSize: 12 },
|
||
valueFormatter: (v: number) => `${v} GB`,
|
||
},
|
||
xAxis: {
|
||
type: 'category',
|
||
data: series.value?.labels ?? [],
|
||
boundaryGap: false,
|
||
axisLine: { lineStyle: { color: t.value.line } },
|
||
axisTick: { show: false },
|
||
axisLabel: { color: t.value.ink3, fontSize: 10.5 },
|
||
},
|
||
yAxis: {
|
||
type: 'value',
|
||
splitLine: { lineStyle: { color: t.value.lineSoft } },
|
||
axisLabel: { color: t.value.ink3, fontSize: 10.5, formatter: '{value}G' },
|
||
},
|
||
series: [
|
||
{
|
||
name: '流出',
|
||
type: 'line',
|
||
data: series.value?.outbound ?? [],
|
||
lineStyle: { color: t.value.chart, width: 2 },
|
||
itemStyle: { color: t.value.chart },
|
||
symbol: 'circle',
|
||
symbolSize: 5,
|
||
},
|
||
{
|
||
name: '流入',
|
||
type: 'line',
|
||
data: series.value?.inbound ?? [],
|
||
lineStyle: { color: t.value.info, width: 2 },
|
||
itemStyle: { color: t.value.info },
|
||
symbol: 'circle',
|
||
symbolSize: 5,
|
||
},
|
||
],
|
||
}))
|
||
</script>
|
||
|
||
<template>
|
||
<div class="panel">
|
||
<div class="flex flex-wrap items-center gap-2.5 border-b border-line-soft px-4.5 py-3.5">
|
||
<div class="text-sm font-semibold">流量</div>
|
||
<div class="flex-1" />
|
||
<div class="w-28">
|
||
<NSelect
|
||
v-model:value="days"
|
||
size="small"
|
||
:options="daysOptions"
|
||
:loading="traffic.loading.value"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div
|
||
v-if="traffic.loading.value && !traffic.data.value"
|
||
class="flex items-center justify-center py-14"
|
||
>
|
||
<NSpin size="small" />
|
||
</div>
|
||
<template v-else-if="traffic.data.value">
|
||
<div class="flex flex-wrap gap-5 px-4.5 pt-3 text-[12.5px] text-ink-2">
|
||
<span>
|
||
<span class="mr-1.5 inline-block h-2 w-2 rounded-full" style="background: var(--color-chart)" />
|
||
流出 · 合计 {{ fmtBytes(traffic.data.value.outboundBytes) }}
|
||
</span>
|
||
<span>
|
||
<span class="mr-1.5 inline-block h-2 w-2 rounded-full" style="background: var(--color-info)" />
|
||
流入 · 合计 {{ fmtBytes(traffic.data.value.inboundBytes) }}
|
||
</span>
|
||
</div>
|
||
<div class="px-3 pb-2">
|
||
<VChart class="w-full" style="height: 210px" :option="option" autoresize />
|
||
</div>
|
||
</template>
|
||
<EmptyCard
|
||
v-else
|
||
title="暂无流量数据"
|
||
note="该实例暂无 Monitoring 数据点(新建实例有数分钟延迟,终止实例不再产生数据)"
|
||
/>
|
||
<FootNote>
|
||
数据来自 Monitoring 指标(VnicToNetworkBytes 发送 / VnicFromNetworkBytes 接收),按日聚合
|
||
</FootNote>
|
||
</div>
|
||
</template>
|