初始提交:OCI 面板前端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:29 +08:00
commit db45a669e3
135 changed files with 25220 additions and 0 deletions
@@ -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>