初始提交:OCI 面板前端(含 GenAI 网关一期)
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user