Finish cpu statistics information
This commit is contained in:
@@ -35,7 +35,7 @@
|
|||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
.key {
|
.key, .value-percent {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
color: constants.$font-main-color;
|
color: constants.$font-main-color;
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.value-chart {
|
.value-chart {
|
||||||
|
justify-content: space-around;
|
||||||
|
width: 0;
|
||||||
>div {
|
>div {
|
||||||
|
max-height: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
>* {
|
>* {
|
||||||
transform: translateY(1px);
|
transform: translateY(1px);
|
||||||
|
|||||||
@@ -1,181 +0,0 @@
|
|||||||
import React, { CSSProperties, useState } from 'react'
|
|
||||||
import echarts, { EChartsCoreOption, EChartsType } from 'echarts/core'
|
|
||||||
import { RendererType } from 'echarts/types/src/util/types'
|
|
||||||
import { LocaleOption } from 'echarts/types/src/core/locale'
|
|
||||||
import { bind, clear } from 'size-sensor'
|
|
||||||
import isEqual from 'fast-deep-equal'
|
|
||||||
import { usePrevious } from '@/util/hooks'
|
|
||||||
|
|
||||||
interface EChartsInitOpts {
|
|
||||||
locale?: string | LocaleOption
|
|
||||||
renderer?: RendererType
|
|
||||||
devicePixelRatio?: number
|
|
||||||
useDirtyRect?: boolean
|
|
||||||
useCoarsePointer?: boolean
|
|
||||||
pointerSize?: number
|
|
||||||
ssr?: boolean
|
|
||||||
width?: number | string
|
|
||||||
height?: number | string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EChartsReactProps {
|
|
||||||
echarts: typeof echarts
|
|
||||||
className?: string
|
|
||||||
style?: CSSProperties
|
|
||||||
option: EChartsCoreOption
|
|
||||||
theme?: string | object | null
|
|
||||||
notMerge?: boolean
|
|
||||||
lazyUpdate?: boolean
|
|
||||||
showLoading?: boolean
|
|
||||||
loadingOption?: object
|
|
||||||
opts?: EChartsInitOpts
|
|
||||||
onChartReady?: (instance: EChartsType) => void
|
|
||||||
onEvents?: Record<string, () => void>
|
|
||||||
shouldSetOption?: (prevProps: EChartsReactProps, props: EChartsReactProps) => boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const EChartReact: React.FC<EChartsReactProps> = (props) => {
|
|
||||||
const elementRef = useRef<HTMLDivElement>(null)
|
|
||||||
const prevProps = usePrevious(props)
|
|
||||||
const [echarts] = useState(props.echarts)
|
|
||||||
const [isInitialResize, setIsInitialResize] = useState(true)
|
|
||||||
|
|
||||||
const { style, className = '', theme, opts } = props
|
|
||||||
|
|
||||||
const renderNewECharts = () => {
|
|
||||||
const { onEvents, onChartReady } = props
|
|
||||||
|
|
||||||
const eChartsInstance = updateEChartsOption()
|
|
||||||
|
|
||||||
bindEvents(eChartsInstance, onEvents || {})
|
|
||||||
|
|
||||||
if (typeof onChartReady === 'function') {
|
|
||||||
onChartReady(eChartsInstance)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elementRef.current) {
|
|
||||||
bind(elementRef.current, () => {
|
|
||||||
resize()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateEChartsOption = () => {
|
|
||||||
const { option, notMerge = false, lazyUpdate = false, showLoading, loadingOption } = props
|
|
||||||
|
|
||||||
const eChartsInstance = getEChartsInstance()
|
|
||||||
eChartsInstance.setOption(option, notMerge, lazyUpdate)
|
|
||||||
if (showLoading) {
|
|
||||||
eChartsInstance.showLoading(loadingOption)
|
|
||||||
} else {
|
|
||||||
eChartsInstance.hideLoading()
|
|
||||||
}
|
|
||||||
|
|
||||||
return eChartsInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
const getEChartsInstance = () =>
|
|
||||||
(elementRef.current && echarts.getInstanceByDom(elementRef.current)) ||
|
|
||||||
echarts.init(elementRef.current, theme, opts)
|
|
||||||
|
|
||||||
const bindEvents = (instance: EChartsType, events: EChartsReactProps['onEvents']) => {
|
|
||||||
const _bindEvents = (
|
|
||||||
eventName: string,
|
|
||||||
func: (param: unknown, instance: EChartsType) => void
|
|
||||||
) => {
|
|
||||||
if (typeof func === 'function') {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
|
|
||||||
instance.on(eventName, (param: unknown) => {
|
|
||||||
func(param, instance)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const eventName in events) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(events, eventName)) {
|
|
||||||
_bindEvents(eventName, events[eventName])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resize = () => {
|
|
||||||
const eChartsInstance = getEChartsInstance()
|
|
||||||
|
|
||||||
if (!isInitialResize) {
|
|
||||||
try {
|
|
||||||
eChartsInstance.resize()
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsInitialResize(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const dispose = () => {
|
|
||||||
if (elementRef.current) {
|
|
||||||
try {
|
|
||||||
clear(elementRef.current)
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e)
|
|
||||||
}
|
|
||||||
echarts.dispose(elementRef.current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const pick = (obj: EChartsReactProps | undefined, keys: string[]): Record<string, unknown> => {
|
|
||||||
const r = {}
|
|
||||||
keys.forEach((key) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-expect-error
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
||||||
r[key] = obj[key]
|
|
||||||
})
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
renderNewECharts()
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const { shouldSetOption } = props
|
|
||||||
if (
|
|
||||||
typeof shouldSetOption === 'function' &&
|
|
||||||
prevProps &&
|
|
||||||
!shouldSetOption(prevProps, props)
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isEqual(prevProps?.theme, props.theme) ||
|
|
||||||
!isEqual(prevProps?.opts, props.opts) ||
|
|
||||||
!isEqual(prevProps?.onEvents, props.onEvents)
|
|
||||||
) {
|
|
||||||
dispose()
|
|
||||||
renderNewECharts()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const pickKeys = ['option', 'notMerge', 'lazyUpdate', 'showLoading', 'loadingOption']
|
|
||||||
if (!isEqual(pick(props, pickKeys), pick(prevProps, pickKeys))) {
|
|
||||||
updateEChartsOption()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isEqual(prevProps?.style, props.style) ||
|
|
||||||
!isEqual(prevProps?.className, props.className)
|
|
||||||
) {
|
|
||||||
resize()
|
|
||||||
}
|
|
||||||
}, [props])
|
|
||||||
|
|
||||||
return <div ref={elementRef} style={style} className={`echarts-react ${className}`}></div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EChartReact
|
|
||||||
1
src/global.d.ts
vendored
1
src/global.d.ts
vendored
@@ -361,6 +361,7 @@ interface CpuInfoVo {
|
|||||||
irq: number
|
irq: number
|
||||||
softirq: number
|
softirq: number
|
||||||
steal: number
|
steal: number
|
||||||
|
total: number
|
||||||
processors: CpuInfoVo[]
|
processors: CpuInfoVo[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import Icon from '@ant-design/icons'
|
import Icon from '@ant-design/icons'
|
||||||
import * as echarts from 'echarts/core'
|
import * as echarts from 'echarts/core'
|
||||||
import {
|
import {
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
GridComponentOption
|
GridComponentOption
|
||||||
} from 'echarts/components'
|
} from 'echarts/components'
|
||||||
import { BarChart, BarSeriesOption } from 'echarts/charts'
|
import { BarChart, BarSeriesOption } from 'echarts/charts'
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
import { SVGRenderer } from 'echarts/renderers'
|
||||||
import '@/assets/css/pages/system/index.scss'
|
import '@/assets/css/pages/system/index.scss'
|
||||||
import { useUpdatedEffect } from '@/util/hooks'
|
import { useUpdatedEffect } from '@/util/hooks'
|
||||||
import { utcToLocalTime } from '@/util/datetime'
|
import { utcToLocalTime } from '@/util/datetime'
|
||||||
@@ -22,12 +22,12 @@ import FlexBox from '@/components/common/FlexBox'
|
|||||||
import FitFullScreen from '@/components/common/FitFullScreen'
|
import FitFullScreen from '@/components/common/FitFullScreen'
|
||||||
import HideScrollbar from '@/components/common/HideScrollbar'
|
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||||
import LoadingMask from '@/components/common/LoadingMask'
|
import LoadingMask from '@/components/common/LoadingMask'
|
||||||
import EChartReact from '@/components/common/echarts/EChartReact'
|
|
||||||
|
|
||||||
echarts.use([TooltipComponent, GridComponent, BarChart, CanvasRenderer])
|
echarts.use([TooltipComponent, GridComponent, BarChart, SVGRenderer])
|
||||||
type EChartsOption = echarts.ComposeOption<
|
type EChartsOption = echarts.ComposeOption<
|
||||||
TooltipComponentOption | GridComponentOption | BarSeriesOption
|
TooltipComponentOption | GridComponentOption | BarSeriesOption
|
||||||
>
|
>
|
||||||
|
|
||||||
interface CommonCardProps extends React.PropsWithChildren {
|
interface CommonCardProps extends React.PropsWithChildren {
|
||||||
icon: IconComponent
|
icon: IconComponent
|
||||||
title: string
|
title: string
|
||||||
@@ -174,47 +174,54 @@ const HardwareInfo: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CPUInfo: React.FC = () => {
|
const CPUInfo: React.FC = () => {
|
||||||
const [cpuInfoData, setCpuInfoData] = useState<BarSeriesOption[]>()
|
const keyDivRef = useRef<HTMLDivElement>(null)
|
||||||
|
const percentDivRef = useRef<HTMLDivElement>(null)
|
||||||
|
const cpuInfoDivRef = useRef<HTMLDivElement>(null)
|
||||||
|
const cpuInfoEChatsRef = useRef<echarts.EChartsType[]>([])
|
||||||
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
const [cpuInfoEChartsOption, setCpuInfoEChartsOption] = useState<EChartsOption[]>([])
|
||||||
|
|
||||||
const defaultSeriesOption: BarSeriesOption = {
|
const defaultSeriesOption: BarSeriesOption = {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
stack: 'total',
|
stack: 'total',
|
||||||
emphasis: {
|
itemStyle: {
|
||||||
focus: 'series'
|
color: (params) => {
|
||||||
|
switch (params.seriesName) {
|
||||||
|
case 'idle':
|
||||||
|
return '#F5F5F5'
|
||||||
|
default:
|
||||||
|
return params.color ?? echarts.color.random()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
valueFormatter: (value) => `${((value as number) * 100).toFixed(2)}%`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useUpdatedEffect(() => {
|
useUpdatedEffect(() => {
|
||||||
setInterval(
|
const intervalId = setInterval(() => getCpuInfo(), 2000)
|
||||||
() =>
|
|
||||||
r_sys_statistics_cpu().then((res) => {
|
const handleOnWindowResize = () => {
|
||||||
const response = res.data
|
setTimeout(() => {
|
||||||
if (response.success) {
|
cpuInfoEChatsRef.current.forEach((value) => value.resize())
|
||||||
const data = response.data
|
}, 50)
|
||||||
if (data) {
|
}
|
||||||
const cpuInfoData = Object.entries(data)
|
|
||||||
.filter(([key]) => key !== 'processors')
|
window.addEventListener('resize', handleOnWindowResize)
|
||||||
.map(([key, value]) => ({
|
|
||||||
...defaultSeriesOption,
|
return () => {
|
||||||
name: key,
|
clearInterval(intervalId)
|
||||||
data: [value as number]
|
window.removeEventListener('resize', handleOnWindowResize)
|
||||||
}))
|
}
|
||||||
console.log(cpuInfoData)
|
|
||||||
setCpuInfoData(cpuInfoData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
5000
|
|
||||||
)
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const option: EChartsOption = {
|
const cpuInfoEChartBaseOption: EChartsOption = {
|
||||||
tooltip: {},
|
tooltip: {},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
data: ['总使用'],
|
|
||||||
axisLine: {
|
axisLine: {
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
@@ -230,30 +237,109 @@ const CPUInfo: React.FC = () => {
|
|||||||
axisPointer: {
|
axisPointer: {
|
||||||
show: false
|
show: false
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
series: cpuInfoData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getCpuInfo = () => {
|
||||||
|
void r_sys_statistics_cpu().then((res) => {
|
||||||
|
const response = res.data
|
||||||
|
if (response.success) {
|
||||||
|
const data = response.data
|
||||||
|
if (data) {
|
||||||
|
if (isLoading) {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const dataList = data.processors.map((value) =>
|
||||||
|
cpuInfoVoToCpuInfoData(value)
|
||||||
|
)
|
||||||
|
dataList.unshift(cpuInfoVoToCpuInfoData(data))
|
||||||
|
|
||||||
|
setCpuInfoEChartsOption(
|
||||||
|
dataList.map((value, index) => ({
|
||||||
|
...cpuInfoEChartBaseOption,
|
||||||
|
yAxis: {
|
||||||
|
...cpuInfoEChartBaseOption.yAxis,
|
||||||
|
data: [index === 0 ? '总占用' : `CPU ${index - 1}`]
|
||||||
|
},
|
||||||
|
series: value
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
if (percentDivRef.current) {
|
||||||
|
percentDivRef.current.innerHTML = ''
|
||||||
|
dataList.forEach((value) => {
|
||||||
|
const percentElement = document.createElement('div')
|
||||||
|
const idle = value.find((item) => item.name === 'idle')?.data[0]
|
||||||
|
percentElement.innerText =
|
||||||
|
idle !== undefined
|
||||||
|
? `${((1 - idle) * 100).toFixed(2)}%`
|
||||||
|
: 'Unknown'
|
||||||
|
percentDivRef.current?.appendChild(percentElement)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cpuInfoDivRef.current?.childElementCount !== dataList.length) {
|
||||||
|
keyDivRef.current && (keyDivRef.current.innerHTML = '')
|
||||||
|
cpuInfoDivRef.current && (cpuInfoDivRef.current.innerHTML = '')
|
||||||
|
for (let i = 0; i < dataList.length; i++) {
|
||||||
|
const keyElement = document.createElement('div')
|
||||||
|
keyElement.innerText = i === 0 ? '总占用' : `CPU ${i - 1}`
|
||||||
|
keyDivRef.current?.appendChild(keyElement)
|
||||||
|
|
||||||
|
const valueElement = document.createElement('div')
|
||||||
|
cpuInfoDivRef.current?.appendChild(valueElement)
|
||||||
|
cpuInfoEChatsRef.current.push(
|
||||||
|
echarts.init(valueElement, null, { renderer: 'svg' })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cpuInfoVoToCpuInfoData = (cpuInfoVo: CpuInfoVo) =>
|
||||||
|
Object.entries(cpuInfoVo)
|
||||||
|
.filter(([key]) => !['total', 'processors'].includes(key))
|
||||||
|
.map(([key, value]) => ({
|
||||||
|
...defaultSeriesOption,
|
||||||
|
name: key,
|
||||||
|
data: [(value as number) / cpuInfoVo.total]
|
||||||
|
}))
|
||||||
|
.sort((a, b) => {
|
||||||
|
const order = [
|
||||||
|
'steal',
|
||||||
|
'irq',
|
||||||
|
'softirq',
|
||||||
|
'iowait',
|
||||||
|
'system',
|
||||||
|
'nice',
|
||||||
|
'user',
|
||||||
|
'idle'
|
||||||
|
]
|
||||||
|
return order.indexOf(a.name) - order.indexOf(b.name)
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
cpuInfoEChatsRef.current?.forEach((value, index) => {
|
||||||
|
try {
|
||||||
|
value.setOption(cpuInfoEChartsOption[index])
|
||||||
|
} catch (e) {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [cpuInfoEChartsOption])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CommonCard icon={IconFatwebCpu} title={'CPU 信息'} loading={false}>
|
<CommonCard icon={IconFatwebCpu} title={'CPU 信息'} loading={isLoading}>
|
||||||
<FlexBox className={'card-content'} direction={'horizontal'}>
|
<FlexBox className={'card-content'} direction={'horizontal'}>
|
||||||
<FlexBox className={'key'}>
|
<FlexBox className={'key'} ref={keyDivRef} />
|
||||||
<div>总占用</div>
|
<FlexBox className={'value-chart'} ref={cpuInfoDivRef} />
|
||||||
<div>总占用</div>
|
<FlexBox className={'value-percent'} ref={percentDivRef} />
|
||||||
<div>总占用</div>
|
|
||||||
</FlexBox>
|
|
||||||
<FlexBox className={'value-chart'}>
|
|
||||||
<div>
|
|
||||||
<EChartReact
|
|
||||||
echarts={echarts}
|
|
||||||
opts={{ renderer: 'svg', height: 12 }}
|
|
||||||
option={option}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</FlexBox>
|
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</CommonCard>
|
</CommonCard>
|
||||||
</>
|
</>
|
||||||
@@ -287,7 +373,6 @@ const System: React.FC = () => {
|
|||||||
<CPUInfo />
|
<CPUInfo />
|
||||||
<MemoryInfo />
|
<MemoryInfo />
|
||||||
<JvmInfo />
|
<JvmInfo />
|
||||||
<div />
|
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</HideScrollbar>
|
</HideScrollbar>
|
||||||
</FitFullScreen>
|
</FitFullScreen>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const useUpdatedEffect = (
|
|||||||
if (isFirstRender.current) {
|
if (isFirstRender.current) {
|
||||||
isFirstRender.current = false
|
isFirstRender.current = false
|
||||||
} else {
|
} else {
|
||||||
effect()
|
return effect()
|
||||||
}
|
}
|
||||||
}, dependencies)
|
}, dependencies)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user