Complete main UI #37
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
@@ -372,4 +372,6 @@ interface MemoryInfoVo {
|
|||||||
virtualMax: number
|
virtualMax: number
|
||||||
swapTotal: number
|
swapTotal: number
|
||||||
swapUsed: number
|
swapUsed: number
|
||||||
|
jvmTotal: number
|
||||||
|
jvmFree: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
const JvmInfo: React.FC = () => {
|
|
||||||
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>
|
||||||
|
|||||||
@@ -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}`
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user