Complete main UI #37

Merged
FatttSnake merged 192 commits from FatttSnake into dev 2024-02-23 16:31:17 +08:00
3 changed files with 322 additions and 50 deletions
Showing only changes of commit 633985af8e - Show all commits

2
src/global.d.ts vendored
View File

@@ -372,4 +372,6 @@ interface MemoryInfoVo {
virtualMax: number virtualMax: number
swapTotal: number swapTotal: number
swapUsed: number swapUsed: number
jvmTotal: number
jvmFree: number
} }

View File

@@ -15,6 +15,7 @@ import { utcToLocalTime } from '@/util/datetime'
import { import {
r_sys_statistics_cpu, r_sys_statistics_cpu,
r_sys_statistics_hardware, r_sys_statistics_hardware,
r_sys_statistics_memory,
r_sys_statistics_software r_sys_statistics_software
} from '@/services/system' } from '@/services/system'
import Card from '@/components/common/Card' import Card from '@/components/common/Card'
@@ -22,6 +23,7 @@ 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 { formatByteSize } from '@/util/common.tsx'
echarts.use([TooltipComponent, GridComponent, BarChart, SVGRenderer]) echarts.use([TooltipComponent, GridComponent, BarChart, SVGRenderer])
type EChartsOption = echarts.ComposeOption< type EChartsOption = echarts.ComposeOption<
@@ -34,6 +36,50 @@ interface CommonCardProps extends React.PropsWithChildren {
loading?: boolean loading?: boolean
} }
const barDefaultSeriesOption: BarSeriesOption = {
type: 'bar',
stack: 'total',
itemStyle: {
color: (params) => {
switch (params.seriesName) {
case 'idle':
case 'free':
return '#F5F5F5'
default:
return params.color ?? echarts.color.random()
}
}
}
}
const eChartsBaseOption: EChartsOption = {
tooltip: {
axisPointer: {
axis: 'x'
}
},
xAxis: {
show: false
},
yAxis: {
axisLine: {
show: false
},
axisLabel: {
show: false
},
axisTick: {
show: false
},
splitLine: {
show: false
},
axisPointer: {
show: false
}
}
}
const CommonCard: React.FC<CommonCardProps> = (props) => { const CommonCard: React.FC<CommonCardProps> = (props) => {
return ( return (
<Card style={{ overflow: 'visible' }}> <Card style={{ overflow: 'visible' }}>
@@ -181,26 +227,15 @@ const CPUInfo: React.FC = () => {
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const [cpuInfoEChartsOption, setCpuInfoEChartsOption] = useState<EChartsOption[]>([]) const [cpuInfoEChartsOption, setCpuInfoEChartsOption] = useState<EChartsOption[]>([])
const defaultSeriesOption: BarSeriesOption = { const cpuDefaultSeriesOption: BarSeriesOption = {
type: 'bar', ...barDefaultSeriesOption,
stack: 'total',
itemStyle: {
color: (params) => {
switch (params.seriesName) {
case 'idle':
return '#F5F5F5'
default:
return params.color ?? echarts.color.random()
}
}
},
tooltip: { tooltip: {
valueFormatter: (value) => `${((value as number) * 100).toFixed(2)}%` valueFormatter: (value) => `${((value as number) * 100).toFixed(2)}%`
} }
} }
useUpdatedEffect(() => { useUpdatedEffect(() => {
const intervalId = setInterval(() => getCpuInfo(), 2000) const intervalId = setInterval(getCpuInfo(), 2000)
const handleOnWindowResize = () => { const handleOnWindowResize = () => {
setTimeout(() => { setTimeout(() => {
@@ -216,30 +251,6 @@ const CPUInfo: React.FC = () => {
} }
}, []) }, [])
const cpuInfoEChartBaseOption: EChartsOption = {
tooltip: {},
xAxis: {
show: false
},
yAxis: {
axisLine: {
show: false
},
axisLabel: {
show: false
},
axisTick: {
show: false
},
splitLine: {
show: false
},
axisPointer: {
show: false
}
}
}
const getCpuInfo = () => { const getCpuInfo = () => {
void r_sys_statistics_cpu().then((res) => { void r_sys_statistics_cpu().then((res) => {
const response = res.data const response = res.data
@@ -258,9 +269,9 @@ const CPUInfo: React.FC = () => {
setCpuInfoEChartsOption( setCpuInfoEChartsOption(
dataList.map((value, index) => ({ dataList.map((value, index) => ({
...cpuInfoEChartBaseOption, ...eChartsBaseOption,
yAxis: { yAxis: {
...cpuInfoEChartBaseOption.yAxis, ...eChartsBaseOption.yAxis,
data: [index === 0 ? '总占用' : `CPU ${index - 1}`] data: [index === 0 ? '总占用' : `CPU ${index - 1}`]
}, },
series: value series: value
@@ -299,13 +310,15 @@ const CPUInfo: React.FC = () => {
} }
} }
}) })
return getCpuInfo
} }
const cpuInfoVoToCpuInfoData = (cpuInfoVo: CpuInfoVo) => const cpuInfoVoToCpuInfoData = (cpuInfoVo: CpuInfoVo) =>
Object.entries(cpuInfoVo) Object.entries(cpuInfoVo)
.filter(([key]) => !['total', 'processors'].includes(key)) .filter(([key]) => !['total', 'processors'].includes(key))
.map(([key, value]) => ({ .map(([key, value]) => ({
...defaultSeriesOption, ...cpuDefaultSeriesOption,
name: key, name: key,
data: [(value as number) / cpuInfoVo.total] data: [(value as number) / cpuInfoVo.total]
})) }))
@@ -347,17 +360,198 @@ const CPUInfo: React.FC = () => {
} }
const MemoryInfo: React.FC = () => { const MemoryInfo: React.FC = () => {
return ( const percentDivRef = useRef<HTMLDivElement>(null)
<> const memoryInfoDivRef = useRef<HTMLDivElement>(null)
<CommonCard icon={IconFatwebMemory} title={'内存信息'} loading={true}></CommonCard> const memoryInfoEChatsRef = useRef<echarts.EChartsType[]>([])
</> const [isLoading, setIsLoading] = useState(true)
) const [memoryInfoEChartsOption, setMemoryInfoEChartsOption] = useState<EChartsOption[]>([])
const memoryDefaultSeriesOption: BarSeriesOption = {
...barDefaultSeriesOption,
tooltip: { valueFormatter: (value) => formatByteSize(value as number) }
} }
const JvmInfo: React.FC = () => { useUpdatedEffect(() => {
const intervalId = setInterval(getMemoryInfo(), 2000)
const handleOnWindowResize = () => {
setTimeout(() => {
memoryInfoEChatsRef.current.forEach((value) => value.resize())
}, 50)
}
window.addEventListener('resize', handleOnWindowResize)
return () => {
clearInterval(intervalId)
window.removeEventListener('resize', handleOnWindowResize)
}
}, [])
const getMemoryInfo = () => {
void r_sys_statistics_memory().then((res) => {
const response = res.data
if (response.success) {
const data = response.data
if (data) {
if (isLoading) {
setIsLoading(false)
}
setTimeout(() => {
const eEchartsOptions = [
{
...eChartsBaseOption,
xAxis: {
...eChartsBaseOption.xAxis,
max: data.total
},
yAxis: {
...eChartsBaseOption.yAxis,
data: ['物理内存']
},
series: [
{
...memoryDefaultSeriesOption,
name: 'used',
data: [data.total - data.free]
},
{
...memoryDefaultSeriesOption,
name: 'free',
data: [data.free]
}
]
},
{
...eChartsBaseOption,
xAxis: {
...eChartsBaseOption.xAxis,
max: data.virtualMax
},
yAxis: {
...eChartsBaseOption.yAxis,
data: ['虚拟']
},
series: [
{
...memoryDefaultSeriesOption,
name: 'used',
data: [data.virtualInUse]
},
{
...memoryDefaultSeriesOption,
name: 'free',
data: [data.virtualMax - data.virtualInUse]
}
]
},
{
...eChartsBaseOption,
xAxis: {
...eChartsBaseOption.xAxis,
max: data.swapTotal
},
yAxis: {
...eChartsBaseOption.yAxis,
data: ['swap']
},
series: [
{
...memoryDefaultSeriesOption,
name: 'used',
data: [data.swapUsed]
},
{
...memoryDefaultSeriesOption,
name: 'free',
data: [data.swapTotal - data.swapUsed]
}
]
},
{
...eChartsBaseOption,
xAxis: {
...eChartsBaseOption.xAxis,
max: data.jvmTotal
},
yAxis: {
...eChartsBaseOption.yAxis,
data: ['jvm 内存']
},
series: [
{
...memoryDefaultSeriesOption,
name: 'used',
data: [data.jvmTotal - data.jvmFree]
},
{
...memoryDefaultSeriesOption,
name: 'free',
data: [data.jvmFree]
}
]
}
]
setMemoryInfoEChartsOption(eEchartsOptions)
if (percentDivRef.current) {
percentDivRef.current.innerHTML = ''
eEchartsOptions.forEach((value) => {
const percentElement = document.createElement('div')
percentElement.innerText = `${(
(value.series[0].data[0] /
(value.series[0].data[0] + value.series[1].data[0])) *
100
).toFixed(2)}%`
percentDivRef.current?.appendChild(percentElement)
})
}
if (!memoryInfoEChatsRef.current.length) {
memoryInfoDivRef.current && (memoryInfoDivRef.current.innerHTML = '')
eEchartsOptions.forEach(() => {
const element = document.createElement('div')
memoryInfoDivRef.current?.appendChild(element)
memoryInfoEChatsRef.current.push(
echarts.init(element, null, { renderer: 'svg' })
)
})
}
})
}
}
})
return getMemoryInfo
}
useEffect(() => {
memoryInfoEChatsRef.current?.forEach((value, index) => {
try {
value.setOption(memoryInfoEChartsOption[index])
} catch (e) {
/* empty */
}
})
}, [memoryInfoEChartsOption])
return ( return (
<> <>
<CommonCard icon={IconFatwebJava} title={'JVM 信息'} loading={true}></CommonCard> <CommonCard icon={IconFatwebMemory} title={'内存信息'} loading={isLoading}>
<FlexBox className={'card-content'} direction={'horizontal'}>
<FlexBox className={'key'}>
<div></div>
<div></div>
<div>swap</div>
<div>jvm </div>
</FlexBox>
<FlexBox className={'value-chart'} ref={memoryInfoDivRef} />
<FlexBox className={'value-percent'} ref={percentDivRef} />
</FlexBox>
</CommonCard>
</> </>
) )
} }
@@ -372,7 +566,6 @@ const System: React.FC = () => {
<HardwareInfo /> <HardwareInfo />
<CPUInfo /> <CPUInfo />
<MemoryInfo /> <MemoryInfo />
<JvmInfo />
</FlexBox> </FlexBox>
</HideScrollbar> </HideScrollbar>
</FitFullScreen> </FitFullScreen>

View File

@@ -1,5 +1,6 @@
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask' import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask'
import { floor } from 'lodash'
export const randomInt = (start: number, end: number) => { export const randomInt = (start: number, end: number) => {
if (start > end) { if (start > end) {
@@ -54,3 +55,79 @@ export const removeLoadingMask = (id: string) => {
value.parentNode?.removeChild(value) value.parentNode?.removeChild(value)
}) })
} }
export enum ByteUnit {
B = 'B',
KiB = 'KiB',
Mib = 'Mib',
GiB = 'GiB',
TiB = 'TiB',
PiB = 'PiB',
EiB = 'EiB',
ZiB = 'ZiB',
YiB = 'YiB'
}
export const formatByteSize = (byteSize: number): string => {
const BASE = 1024
if (byteSize <= -1) {
return byteSize.toString()
}
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.B)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.KiB)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.Mib)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.GiB)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.TiB)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.PiB)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.EiB)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.ZiB)
}
byteSize /= BASE
return formatByte(byteSize, ByteUnit.YiB)
}
const formatByte = (size: number, unit: ByteUnit): string => {
let precision
if ((size * 1000) % 10 > 0) {
precision = 3
} else if ((size * 100) % 10 > 0) {
precision = 2
} else if ((size * 10) % 10 > 0) {
precision = 1
} else {
precision = 0
}
return `${size.toFixed(precision)}${unit}`
}