mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
* refactor: nodes analytics * feat(debug): add pprof in debug mode * refactor: websocket error handler
462 lines
12 KiB
Vue
462 lines
12 KiB
Vue
<script setup lang="ts">
|
|
import type { CPUInfoStat, DiskStat, HostInfoStat, LoadStat, MemStat } from '@/api/analytic'
|
|
import type { Series } from '@/components/Chart/types'
|
|
import type ReconnectingWebSocket from 'reconnecting-websocket'
|
|
import analytic from '@/api/analytic'
|
|
import AreaChart from '@/components/Chart/AreaChart.vue'
|
|
import RadialBarChart from '@/components/Chart/RadialBarChart.vue'
|
|
import { bytesToSize } from '@/lib/helper'
|
|
import { useSettingsStore } from '@/pinia'
|
|
|
|
let websocket: ReconnectingWebSocket | WebSocket
|
|
|
|
const settings = useSettingsStore()
|
|
|
|
const { language } = storeToRefs(settings)
|
|
|
|
const rerender = ref(0)
|
|
|
|
watch(language, () => {
|
|
rerender.value += 1
|
|
})
|
|
|
|
const host: HostInfoStat = reactive({
|
|
platform: '',
|
|
platformVersion: '',
|
|
os: '',
|
|
kernelVersion: '',
|
|
kernelArch: '',
|
|
}) as HostInfoStat
|
|
|
|
const cpu = ref('0.0')
|
|
const cpu_info = reactive([]) as CPUInfoStat[]
|
|
const cpu_analytic_series = reactive([{ name: 'User', data: [] }, { name: 'Total', data: [] }]) as Series[]
|
|
|
|
const net_analytic = reactive([{ name: $gettext('Receive'), data: [] }, { name: $gettext('Send'), data: [] }]) as Series[]
|
|
|
|
const disk_io_analytic = reactive([{ name: $gettext('Writes'), data: [] }, { name: $gettext('Reads'), data: [] }]) as Series[]
|
|
|
|
const memory = reactive({
|
|
total: '',
|
|
used: '',
|
|
cached: '',
|
|
free: '',
|
|
swap_used: '',
|
|
swap_cached: '',
|
|
swap_percent: 0,
|
|
swap_total: '',
|
|
pressure: 0,
|
|
}) as MemStat
|
|
|
|
const disk = reactive({ percentage: 0, used: '', total: '', writes: { x: '', y: 0 }, reads: { x: '', y: 0 } }) as DiskStat
|
|
const disk_io = reactive({ writes: 0, reads: 0 })
|
|
const uptime = ref('')
|
|
const loadavg = reactive({ load1: 0, load5: 0, load15: 0 }) as LoadStat
|
|
const net = reactive({ recv: 0, sent: 0, last_recv: 0, last_sent: 0 })
|
|
|
|
function net_formatter(bytes: number) {
|
|
return `${bytesToSize(bytes)}/s`
|
|
}
|
|
|
|
function cpu_formatter(usage: number) {
|
|
return usage.toFixed(2)
|
|
}
|
|
|
|
onMounted(() => {
|
|
analytic.init().then(r => {
|
|
Object.assign(host, r.host)
|
|
Object.assign(cpu_info, r.cpu.info)
|
|
Object.assign(memory, r.memory)
|
|
Object.assign(disk, r.disk)
|
|
|
|
// uptime
|
|
handle_uptime(r.host?.uptime)
|
|
|
|
// load_avg
|
|
Object.assign(loadavg, r.loadavg)
|
|
|
|
net.last_recv = r.network.init.bytesRecv
|
|
net.last_sent = r.network.init.bytesSent
|
|
|
|
cpu_analytic_series[0].data = cpu_analytic_series[0].data.concat(r.cpu.user)
|
|
cpu_analytic_series[1].data = cpu_analytic_series[1].data.concat(r.cpu.total)
|
|
net_analytic[0].data = net_analytic[0].data.concat(r.network.bytesRecv)
|
|
net_analytic[1].data = net_analytic[1].data.concat(r.network.bytesSent)
|
|
disk_io_analytic[0].data = disk_io_analytic[0].data.concat(r.disk_io.writes)
|
|
disk_io_analytic[1].data = disk_io_analytic[1].data.concat(r.disk_io.reads)
|
|
|
|
websocket = analytic.server()
|
|
websocket.onmessage = wsOnMessage
|
|
})
|
|
})
|
|
|
|
function handle_uptime(t: number) {
|
|
// uptime
|
|
let _uptime = Math.floor(t)
|
|
const uptime_days = Math.floor(_uptime / 86400)
|
|
|
|
_uptime -= uptime_days * 86400
|
|
|
|
const uptime_hours = Math.floor(_uptime / 3600)
|
|
|
|
_uptime -= uptime_hours * 3600
|
|
uptime.value = `${uptime_days}d ${uptime_hours}h ${Math.floor(_uptime / 60)}m`
|
|
}
|
|
|
|
function wsOnMessage(m: MessageEvent) {
|
|
const r = JSON.parse(m.data)
|
|
|
|
const cpu_usage = Math.min(r.cpu.system + r.cpu.user, 100)
|
|
|
|
cpu.value = cpu_usage.toFixed(2)
|
|
|
|
const time = new Date().toLocaleString()
|
|
|
|
cpu_analytic_series[0].data.push({ x: time, y: r.cpu.user.toFixed(2) })
|
|
cpu_analytic_series[1].data.push({ x: time, y: cpu_usage })
|
|
|
|
if (cpu_analytic_series[0].data.length > 100) {
|
|
cpu_analytic_series[0].data.shift()
|
|
cpu_analytic_series[1].data.shift()
|
|
}
|
|
|
|
// mem
|
|
Object.assign(memory, r.memory)
|
|
|
|
// disk
|
|
Object.assign(disk, r.disk)
|
|
disk_io.writes = r.disk.writes.y
|
|
disk_io.reads = r.disk.reads.y
|
|
|
|
// uptime
|
|
handle_uptime(r.uptime)
|
|
|
|
// loadavg
|
|
Object.assign(loadavg, r.loadavg)
|
|
|
|
// network
|
|
Object.assign(net, r.network)
|
|
net.recv = r.network.bytesRecv - net.last_recv
|
|
net.sent = r.network.bytesSent - net.last_sent
|
|
net.last_recv = r.network.bytesRecv
|
|
net.last_sent = r.network.bytesSent
|
|
|
|
net_analytic[0].data.push({ x: time, y: net.recv })
|
|
net_analytic[1].data.push({ x: time, y: net.sent })
|
|
|
|
if (net_analytic[0].data.length > 100) {
|
|
net_analytic[0].data.shift()
|
|
net_analytic[1].data.shift()
|
|
}
|
|
|
|
disk_io_analytic[0].data.push(r.disk.writes)
|
|
disk_io_analytic[1].data.push(r.disk.reads)
|
|
|
|
if (disk_io_analytic[0].data.length > 100) {
|
|
disk_io_analytic[0].data.shift()
|
|
disk_io_analytic[1].data.shift()
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div :key="rerender">
|
|
<ARow
|
|
:gutter="[{ xs: 0, sm: 16 }, 16]"
|
|
class="first-row"
|
|
>
|
|
<ACol
|
|
:xl="7"
|
|
:lg="24"
|
|
:md="24"
|
|
:xs="24"
|
|
>
|
|
<ACard
|
|
:title="$gettext('Server Info')"
|
|
:bordered="false"
|
|
>
|
|
<p>
|
|
{{ $gettext('Uptime:') }}
|
|
{{ uptime }}
|
|
</p>
|
|
<p>
|
|
{{ $gettext('Load Average:') }}
|
|
<span class="load-avg-describe"> 1min:</span>{{ loadavg?.load1?.toFixed(2) }}
|
|
<span class="load-avg-describe"> | 5min:</span>{{ loadavg?.load5?.toFixed(2) }}
|
|
<span class="load-avg-describe"> | 15min:</span>{{ loadavg?.load15?.toFixed(2) }}
|
|
</p>
|
|
<p>
|
|
{{ $gettext('OS:') }}
|
|
<span class="os-platform">{{ ` ${host.platform}` }}</span> {{ host.platformVersion }}
|
|
<span class="os-info">({{ host.os }} {{ host.kernelVersion }}
|
|
{{ host.kernelArch }})</span>
|
|
</p>
|
|
<p v-if="cpu_info">
|
|
{{ `${$gettext('CPU:')} ` }}
|
|
<span class="cpu-model">{{ cpu_info[0]?.modelName || 'Core' }}</span>
|
|
<span class="cpu-mhz">{{
|
|
cpu_info[0]?.mhz > 0.01 ? `${(cpu_info[0]?.mhz / 1000).toFixed(2)}GHz` : 'Core'
|
|
}}</span>
|
|
* {{ cpu_info.length }}
|
|
</p>
|
|
</ACard>
|
|
</ACol>
|
|
<ACol
|
|
:xl="10"
|
|
:lg="16"
|
|
:md="24"
|
|
:xs="24"
|
|
class="chart_dashboard"
|
|
>
|
|
<ACard
|
|
:title="$gettext('Memory and Storage')"
|
|
:bordered="false"
|
|
>
|
|
<ARow :gutter="[0, 16]">
|
|
<ACol
|
|
:xs="24"
|
|
:sm="24"
|
|
:md="8"
|
|
>
|
|
<RadialBarChart
|
|
:key="$gettext('Memory')"
|
|
:name="$gettext('Memory')"
|
|
:series="[memory.pressure]"
|
|
:center-text="memory.used"
|
|
:bottom-text="memory.total"
|
|
colors="#36a3eb"
|
|
/>
|
|
</ACol>
|
|
<ACol
|
|
:xs="24"
|
|
:sm="12"
|
|
:md="8"
|
|
>
|
|
<RadialBarChart
|
|
:key="$gettext('Swap')"
|
|
:name="$gettext('Swap')"
|
|
:series="[memory.swap_percent]"
|
|
:center-text="memory.swap_used"
|
|
:bottom-text="memory.swap_total"
|
|
colors="#ff6385"
|
|
/>
|
|
</ACol>
|
|
<ACol
|
|
:xs="24"
|
|
:sm="12"
|
|
:md="8"
|
|
>
|
|
<RadialBarChart
|
|
:key="$gettext('Storage')"
|
|
:name="$gettext('Storage')"
|
|
:series="[disk.percentage]"
|
|
:center-text="disk.used"
|
|
:bottom-text="disk.total"
|
|
colors="#87d068"
|
|
/>
|
|
</ACol>
|
|
</ARow>
|
|
</ACard>
|
|
</ACol>
|
|
<ACol
|
|
:xl="7"
|
|
:lg="8"
|
|
:sm="24"
|
|
:xs="24"
|
|
class="chart_dashboard network-total"
|
|
>
|
|
<ACard
|
|
:title="$gettext('Network Statistics')"
|
|
:bordered="false"
|
|
>
|
|
<ARow :gutter="16">
|
|
<ACol :span="12">
|
|
<AStatistic
|
|
:value="bytesToSize(net.last_recv)"
|
|
:title="$gettext('Network Total Receive')"
|
|
/>
|
|
</ACol>
|
|
<ACol :span="12">
|
|
<AStatistic
|
|
:value="bytesToSize(net.last_sent)"
|
|
:title="$gettext('Network Total Send')"
|
|
/>
|
|
</ACol>
|
|
</ARow>
|
|
</ACard>
|
|
</ACol>
|
|
</ARow>
|
|
<ARow
|
|
:gutter="[{ xs: 0, sm: 16 }, 16]"
|
|
class="row-two"
|
|
>
|
|
<ACol
|
|
:xl="8"
|
|
:lg="24"
|
|
:md="24"
|
|
:sm="24"
|
|
:xs="24"
|
|
>
|
|
<ACard
|
|
:title="$gettext('CPU Status')"
|
|
:bordered="false"
|
|
>
|
|
<AStatistic
|
|
:value="cpu"
|
|
title="CPU"
|
|
>
|
|
<template #suffix>
|
|
<span>%</span>
|
|
</template>
|
|
</AStatistic>
|
|
<AreaChart
|
|
:series="cpu_analytic_series"
|
|
:y-formatter="cpu_formatter"
|
|
:max="100"
|
|
/>
|
|
</ACard>
|
|
</ACol>
|
|
<ACol
|
|
:xl="8"
|
|
:lg="12"
|
|
:md="24"
|
|
:sm="24"
|
|
:xs="24"
|
|
>
|
|
<ACard
|
|
:title="$gettext('Network')"
|
|
:bordered="false"
|
|
>
|
|
<ARow :gutter="16">
|
|
<ACol :span="12">
|
|
<AStatistic
|
|
:value="bytesToSize(net.recv)"
|
|
:title="$gettext('Receive')"
|
|
>
|
|
<template #suffix>
|
|
<span>/s</span>
|
|
</template>
|
|
</AStatistic>
|
|
</ACol>
|
|
<ACol :span="12">
|
|
<AStatistic
|
|
:value="bytesToSize(net.sent)"
|
|
:title="$gettext('Send')"
|
|
>
|
|
<template #suffix>
|
|
<span>/s</span>
|
|
</template>
|
|
</AStatistic>
|
|
</ACol>
|
|
</ARow>
|
|
<AreaChart
|
|
:series="net_analytic"
|
|
:y-formatter="net_formatter"
|
|
/>
|
|
</ACard>
|
|
</ACol>
|
|
<ACol
|
|
:xl="8"
|
|
:lg="12"
|
|
:md="24"
|
|
:sm="24"
|
|
:xs="24"
|
|
>
|
|
<ACard
|
|
:title="$gettext('Disk IO')"
|
|
:bordered="false"
|
|
>
|
|
<ARow :gutter="16">
|
|
<ACol :span="12">
|
|
<AStatistic
|
|
:value="disk_io.writes"
|
|
:title="$gettext('Writes')"
|
|
>
|
|
<template #suffix>
|
|
<span>/s</span>
|
|
</template>
|
|
</AStatistic>
|
|
</ACol>
|
|
<ACol :span="12">
|
|
<AStatistic
|
|
:value="disk_io.reads"
|
|
:title="$gettext('Reads')"
|
|
>
|
|
<template #suffix>
|
|
<span>/s</span>
|
|
</template>
|
|
</AStatistic>
|
|
</ACol>
|
|
</ARow>
|
|
<AreaChart :series="disk_io_analytic" />
|
|
</ACard>
|
|
</ACol>
|
|
</ARow>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="less" scoped>
|
|
.first-row {
|
|
.ant-card {
|
|
min-height: 227px;
|
|
|
|
p {
|
|
margin-bottom: 8px;
|
|
}
|
|
}
|
|
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.ant-card {
|
|
.ant-statistic {
|
|
margin: 0 0 10px 10px
|
|
}
|
|
|
|
.chart {
|
|
max-width: 800px;
|
|
max-height: 350px;
|
|
}
|
|
|
|
.chart_dashboard {
|
|
padding: 60px;
|
|
|
|
.description {
|
|
width: 120px;
|
|
text-align: center
|
|
}
|
|
}
|
|
|
|
@media (max-width: 512px) {
|
|
margin: 10px 0;
|
|
.chart_dashboard {
|
|
padding: 20px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.load-avg-describe {
|
|
margin-right: 2px;
|
|
@media (max-width: 1600px) and (min-width: 1200px) {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.os-info {
|
|
@media (max-width: 1600px) and (min-width: 1200px) {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.cpu-model {
|
|
@media (max-width: 1790px) and (min-width: 1200px) {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.cpu-mhz {
|
|
@media (min-width: 1790px) or (max-width: 1200px) {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|