Complete main UI #37

Merged
FatttSnake merged 192 commits from FatttSnake into dev 2024-02-23 16:31:17 +08:00
5 changed files with 182 additions and 151 deletions
Showing only changes of commit b692ba7af6 - Show all commits

View File

@@ -28,6 +28,11 @@
display: flex; display: flex;
font-size: 1.2em; font-size: 1.2em;
} }
:nth-child(n+3) {
flex: 0 0 auto;
color: constants.$font-main-color;
}
} }
.card-content { .card-content {

View File

@@ -15,8 +15,7 @@ export const URL_SYS_STATISTICS = '/system/statistics'
export const URL_SYS_STATISTICS_SOFTWARE = `${URL_SYS_STATISTICS}/software` export const URL_SYS_STATISTICS_SOFTWARE = `${URL_SYS_STATISTICS}/software`
export const URL_SYS_STATISTICS_HARDWARE = `${URL_SYS_STATISTICS}/hardware` export const URL_SYS_STATISTICS_HARDWARE = `${URL_SYS_STATISTICS}/hardware`
export const URL_SYS_STATISTICS_CPU = `${URL_SYS_STATISTICS}/cpu` export const URL_SYS_STATISTICS_CPU = `${URL_SYS_STATISTICS}/cpu`
export const URL_SYS_STATISTICS_MEMORY = `${URL_SYS_STATISTICS}/memory` export const URL_SYS_STATISTICS_STORAGE = `${URL_SYS_STATISTICS}/storage`
export const URL_SYS_STATISTICS_JVM = `${URL_SYS_STATISTICS}/jvm`
export const URL_API_V1 = '/api/v1' export const URL_API_V1 = '/api/v1'
export const URL_API_V1_AVATAR_RANDOM_BASE64 = `${URL_API_V1}/avatar/base64` export const URL_API_V1_AVATAR_RANDOM_BASE64 = `${URL_API_V1}/avatar/base64`

17
src/global.d.ts vendored
View File

@@ -365,13 +365,20 @@ interface CpuInfoVo {
processors: CpuInfoVo[] processors: CpuInfoVo[]
} }
interface MemoryInfoVo { interface StorageInfoVo {
total: number memoryTotal: number
free: number memoryFree: number
virtualInUse: number virtualMemoryInUse: number
virtualMax: number virtualMemoryMax: number
swapTotal: number swapTotal: number
swapUsed: number swapUsed: number
jvmTotal: number jvmTotal: number
jvmFree: number jvmFree: number
fileStores: FileStoreInfoVo[]
}
interface FileStoreInfoVo {
mount: string
total: number
free: number
} }

View File

@@ -11,31 +11,25 @@ import { BarChart, BarSeriesOption } from 'echarts/charts'
import { SVGRenderer } 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 { formatByteSize } from '@/util/common'
import { utcToLocalTime } from '@/util/datetime' 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 r_sys_statistics_storage
} from '@/services/system' } from '@/services/system'
import Card from '@/components/common/Card' import Card from '@/components/common/Card'
import FlexBox from '@/components/common/FlexBox' 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<
TooltipComponentOption | GridComponentOption | BarSeriesOption TooltipComponentOption | GridComponentOption | BarSeriesOption
> >
interface CommonCardProps extends React.PropsWithChildren {
icon: IconComponent
title: string
loading?: boolean
}
const barDefaultSeriesOption: BarSeriesOption = { const barDefaultSeriesOption: BarSeriesOption = {
type: 'bar', type: 'bar',
stack: 'total', stack: 'total',
@@ -76,6 +70,13 @@ const eChartsBaseOption: EChartsOption = {
} }
} }
interface CommonCardProps extends React.PropsWithChildren {
icon: IconComponent
title: string
loading?: boolean
expand?: React.ReactNode
}
const CommonCard: React.FC<CommonCardProps> = (props) => { const CommonCard: React.FC<CommonCardProps> = (props) => {
return ( return (
<Card style={{ overflow: 'visible' }}> <Card style={{ overflow: 'visible' }}>
@@ -83,6 +84,7 @@ const CommonCard: React.FC<CommonCardProps> = (props) => {
<FlexBox direction={'horizontal'} className={'head'}> <FlexBox direction={'horizontal'} className={'head'}>
<Icon component={props.icon} className={'icon'} /> <Icon component={props.icon} className={'icon'} />
<div className={'title'}>{props.title}</div> <div className={'title'}>{props.title}</div>
{props.expand}
</FlexBox> </FlexBox>
<LoadingMask <LoadingMask
hidden={!props.loading} hidden={!props.loading}
@@ -221,6 +223,7 @@ const CPUInfo: React.FC = () => {
const cpuInfoDivRef = useRef<HTMLDivElement>(null) const cpuInfoDivRef = useRef<HTMLDivElement>(null)
const cpuInfoEChatsRef = useRef<echarts.EChartsType[]>([]) const cpuInfoEChatsRef = useRef<echarts.EChartsType[]>([])
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const [refreshInterval, setRefreshInterval] = useState('5')
const [cpuInfoEChartsOption, setCpuInfoEChartsOption] = useState<EChartsOption[]>([]) const [cpuInfoEChartsOption, setCpuInfoEChartsOption] = useState<EChartsOption[]>([])
const cpuDefaultSeriesOption: BarSeriesOption = { const cpuDefaultSeriesOption: BarSeriesOption = {
@@ -231,8 +234,6 @@ const CPUInfo: React.FC = () => {
} }
useUpdatedEffect(() => { useUpdatedEffect(() => {
const intervalId = setInterval(getCpuInfo(), 2000)
const handleOnWindowResize = () => { const handleOnWindowResize = () => {
setTimeout(() => { setTimeout(() => {
cpuInfoEChatsRef.current.forEach((value) => value.resize()) cpuInfoEChatsRef.current.forEach((value) => value.resize())
@@ -242,11 +243,18 @@ const CPUInfo: React.FC = () => {
window.addEventListener('resize', handleOnWindowResize) window.addEventListener('resize', handleOnWindowResize)
return () => { return () => {
clearInterval(intervalId)
window.removeEventListener('resize', handleOnWindowResize) window.removeEventListener('resize', handleOnWindowResize)
} }
}, []) }, [])
useUpdatedEffect(() => {
const intervalId = setInterval(getCpuInfo(), parseInt(refreshInterval) * 1000)
return () => {
clearInterval(intervalId)
}
}, [refreshInterval])
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
@@ -287,7 +295,7 @@ const CPUInfo: React.FC = () => {
}) })
} }
if (cpuInfoDivRef.current?.childElementCount !== dataList.length) { if (!cpuInfoEChatsRef.current.length) {
keyDivRef.current && (keyDivRef.current.innerHTML = '') keyDivRef.current && (keyDivRef.current.innerHTML = '')
cpuInfoDivRef.current && (cpuInfoDivRef.current.innerHTML = '') cpuInfoDivRef.current && (cpuInfoDivRef.current.innerHTML = '')
for (let i = 0; i < dataList.length; i++) { for (let i = 0; i < dataList.length; i++) {
@@ -344,7 +352,31 @@ const CPUInfo: React.FC = () => {
return ( return (
<> <>
<CommonCard icon={IconFatwebCpu} title={'CPU 信息'} loading={isLoading}> <CommonCard
icon={IconFatwebCpu}
title={'CPU 信息'}
loading={isLoading}
expand={
<AntdSelect
value={refreshInterval}
onChange={(value) => setRefreshInterval(value)}
>
<AntdSelect.Option key={1}>1</AntdSelect.Option>
<AntdSelect.Option key={2}>2</AntdSelect.Option>
<AntdSelect.Option key={3}>3</AntdSelect.Option>
<AntdSelect.Option key={5}>5</AntdSelect.Option>
<AntdSelect.Option key={10}>10</AntdSelect.Option>
<AntdSelect.Option key={15}>15</AntdSelect.Option>
<AntdSelect.Option key={20}>20</AntdSelect.Option>
<AntdSelect.Option key={30}>30</AntdSelect.Option>
<AntdSelect.Option key={60}>60</AntdSelect.Option>
<AntdSelect.Option key={120}>2</AntdSelect.Option>
<AntdSelect.Option key={180}>3</AntdSelect.Option>
<AntdSelect.Option key={300}>5</AntdSelect.Option>
<AntdSelect.Option key={600}>10</AntdSelect.Option>
</AntdSelect>
}
>
<FlexBox className={'card-content'} direction={'horizontal'}> <FlexBox className={'card-content'} direction={'horizontal'}>
<FlexBox className={'key'} ref={keyDivRef} /> <FlexBox className={'key'} ref={keyDivRef} />
<FlexBox className={'value-chart'} ref={cpuInfoDivRef} /> <FlexBox className={'value-chart'} ref={cpuInfoDivRef} />
@@ -355,37 +387,44 @@ const CPUInfo: React.FC = () => {
) )
} }
const MemoryInfo: React.FC = () => { const StorageInfo: React.FC = () => {
const keyDivRef = useRef<HTMLDivElement>(null)
const percentDivRef = useRef<HTMLDivElement>(null) const percentDivRef = useRef<HTMLDivElement>(null)
const memoryInfoDivRef = useRef<HTMLDivElement>(null) const storageInfoDivRef = useRef<HTMLDivElement>(null)
const memoryInfoEChatsRef = useRef<echarts.EChartsType[]>([]) const storageInfoEChatsRef = useRef<echarts.EChartsType[]>([])
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const [memoryInfoEChartsOption, setMemoryInfoEChartsOption] = useState<EChartsOption[]>([]) const [refreshInterval, setRefreshInterval] = useState('5')
const [storageInfoEChartsOption, setStorageInfoEChartsOption] = useState<EChartsOption[]>([])
const memoryDefaultSeriesOption: BarSeriesOption = { const storageDefaultSeriesOption: BarSeriesOption = {
...barDefaultSeriesOption, ...barDefaultSeriesOption,
tooltip: { valueFormatter: (value) => formatByteSize(value as number) } tooltip: { valueFormatter: (value) => formatByteSize(value as number) }
} }
useUpdatedEffect(() => { useUpdatedEffect(() => {
const intervalId = setInterval(getMemoryInfo(), 2000)
const handleOnWindowResize = () => { const handleOnWindowResize = () => {
setTimeout(() => { setTimeout(() => {
memoryInfoEChatsRef.current.forEach((value) => value.resize()) storageInfoEChatsRef.current.forEach((value) => value.resize())
}, 50) }, 50)
} }
window.addEventListener('resize', handleOnWindowResize) window.addEventListener('resize', handleOnWindowResize)
return () => { return () => {
clearInterval(intervalId)
window.removeEventListener('resize', handleOnWindowResize) window.removeEventListener('resize', handleOnWindowResize)
} }
}, []) }, [])
const getMemoryInfo = () => { useUpdatedEffect(() => {
void r_sys_statistics_memory().then((res) => { const intervalId = setInterval(getStorageInfo(), parseInt(refreshInterval) * 1000)
return () => {
clearInterval(intervalId)
}
}, [refreshInterval])
const getStorageInfo = () => {
void r_sys_statistics_storage().then((res) => {
const response = res.data const response = res.data
if (response.success) { if (response.success) {
const data = response.data const data = response.data
@@ -395,123 +434,64 @@ const MemoryInfo: React.FC = () => {
} }
setTimeout(() => { setTimeout(() => {
const eEchartsOption = [ const eChartsOptions = [
{ storageInfoVoToStorageEChartsOption(
...eChartsBaseOption, '物理内存',
xAxis: { data.memoryTotal - data.memoryFree,
...eChartsBaseOption.xAxis, data.memoryFree
max: data.total ),
}, storageInfoVoToStorageEChartsOption(
yAxis: { '虚拟内存',
...eChartsBaseOption.yAxis, data.virtualMemoryInUse,
data: ['物理内存'] data.virtualMemoryMax - data.virtualMemoryInUse
}, ),
series: [ storageInfoVoToStorageEChartsOption(
{ 'swap',
...memoryDefaultSeriesOption, data.swapUsed,
name: 'used', data.swapTotal - data.swapUsed
data: [data.total - data.free] ),
}, storageInfoVoToStorageEChartsOption(
{ 'jvm 内存',
...memoryDefaultSeriesOption, data.jvmTotal - data.jvmFree,
name: 'free', data.jvmFree
data: [data.free] )
}
] ]
}, data.fileStores.forEach((value) =>
{ eChartsOptions.push(
...eChartsBaseOption, storageInfoVoToStorageEChartsOption(
xAxis: { value.mount,
...eChartsBaseOption.xAxis, value.total - value.free,
max: data.virtualMax value.free
}, )
yAxis: { )
...eChartsBaseOption.yAxis, )
data: ['虚拟'] setStorageInfoEChartsOption(eChartsOptions)
},
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(eEchartsOption)
if (percentDivRef.current) { if (percentDivRef.current && keyDivRef.current) {
keyDivRef.current.innerHTML = ''
percentDivRef.current.innerHTML = '' percentDivRef.current.innerHTML = ''
eEchartsOption.forEach((value) => { eChartsOptions.forEach((value) => {
const keyElement = document.createElement('div')
const percentElement = document.createElement('div') const percentElement = document.createElement('div')
keyElement.innerText = value.yAxis.data[0]
percentElement.innerText = `${( percentElement.innerText = `${(
(value.series[0].data[0] / (value.series[0].data[0] /
(value.series[0].data[0] + value.series[1].data[0])) * (value.series[0].data[0] + value.series[1].data[0])) *
100 100
).toFixed(2)}%` ).toFixed(2)}%`
keyDivRef.current?.appendChild(keyElement)
percentDivRef.current?.appendChild(percentElement) percentDivRef.current?.appendChild(percentElement)
}) })
} }
if (!memoryInfoEChatsRef.current.length) { if (!storageInfoEChatsRef.current.length) {
memoryInfoDivRef.current && (memoryInfoDivRef.current.innerHTML = '') storageInfoDivRef.current && (storageInfoDivRef.current.innerHTML = '')
eEchartsOption.forEach(() => { eChartsOptions.forEach(() => {
const element = document.createElement('div') const element = document.createElement('div')
memoryInfoDivRef.current?.appendChild(element) storageInfoDivRef.current?.appendChild(element)
memoryInfoEChatsRef.current.push( storageInfoEChatsRef.current.push(
echarts.init(element, null, { renderer: 'svg' }) echarts.init(element, null, { renderer: 'svg' })
) )
}) })
@@ -521,30 +501,73 @@ const MemoryInfo: React.FC = () => {
} }
}) })
return getMemoryInfo return getStorageInfo
} }
const storageInfoVoToStorageEChartsOption = (label: string, used: number, free: number) => ({
...eChartsBaseOption,
xAxis: {
...eChartsBaseOption.xAxis,
max: used + free
},
yAxis: {
...eChartsBaseOption.yAxis,
data: [label]
},
series: [
{
...storageDefaultSeriesOption,
name: 'used',
data: [used]
},
{
...storageDefaultSeriesOption,
name: 'free',
data: [free]
}
]
})
useEffect(() => { useEffect(() => {
memoryInfoEChatsRef.current?.forEach((value, index) => { storageInfoEChatsRef.current?.forEach((value, index) => {
try { try {
value.setOption(memoryInfoEChartsOption[index]) value.setOption(storageInfoEChartsOption[index])
} catch (e) { } catch (e) {
/* empty */ /* empty */
} }
}) })
}, [memoryInfoEChartsOption]) }, [storageInfoEChartsOption])
return ( return (
<> <>
<CommonCard icon={IconFatwebMemory} title={'内存信息'} loading={isLoading}> <CommonCard
icon={IconFatwebMemory}
title={'内存信息'}
loading={isLoading}
expand={
<AntdSelect
value={refreshInterval}
onChange={(value) => setRefreshInterval(value)}
>
<AntdSelect.Option key={1}>1</AntdSelect.Option>
<AntdSelect.Option key={2}>2</AntdSelect.Option>
<AntdSelect.Option key={3}>3</AntdSelect.Option>
<AntdSelect.Option key={5}>5</AntdSelect.Option>
<AntdSelect.Option key={10}>10</AntdSelect.Option>
<AntdSelect.Option key={15}>15</AntdSelect.Option>
<AntdSelect.Option key={20}>20</AntdSelect.Option>
<AntdSelect.Option key={30}>30</AntdSelect.Option>
<AntdSelect.Option key={60}>60</AntdSelect.Option>
<AntdSelect.Option key={120}>2</AntdSelect.Option>
<AntdSelect.Option key={180}>3</AntdSelect.Option>
<AntdSelect.Option key={300}>5</AntdSelect.Option>
<AntdSelect.Option key={600}>10</AntdSelect.Option>
</AntdSelect>
}
>
<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={storageInfoDivRef} />
<div></div>
<div>swap</div>
<div>jvm </div>
</FlexBox>
<FlexBox className={'value-chart'} ref={memoryInfoDivRef} />
<FlexBox className={'value-percent'} ref={percentDivRef} /> <FlexBox className={'value-percent'} ref={percentDivRef} />
</FlexBox> </FlexBox>
</CommonCard> </CommonCard>
@@ -561,7 +584,7 @@ const System: React.FC = () => {
<SoftwareInfo /> <SoftwareInfo />
<HardwareInfo /> <HardwareInfo />
<CPUInfo /> <CPUInfo />
<MemoryInfo /> <StorageInfo />
</FlexBox> </FlexBox>
</HideScrollbar> </HideScrollbar>
</FitFullScreen> </FitFullScreen>

View File

@@ -12,8 +12,7 @@ import {
URL_SYS_STATISTICS_SOFTWARE, URL_SYS_STATISTICS_SOFTWARE,
URL_SYS_STATISTICS_HARDWARE, URL_SYS_STATISTICS_HARDWARE,
URL_SYS_STATISTICS_CPU, URL_SYS_STATISTICS_CPU,
URL_SYS_STATISTICS_MEMORY, URL_SYS_STATISTICS_STORAGE
URL_SYS_STATISTICS_JVM
} from '@/constants/urls.constants' } from '@/constants/urls.constants'
import request from '@/services/index' import request from '@/services/index'
@@ -86,6 +85,4 @@ export const r_sys_statistics_hardware = () =>
export const r_sys_statistics_cpu = () => request.get<CpuInfoVo>(URL_SYS_STATISTICS_CPU) export const r_sys_statistics_cpu = () => request.get<CpuInfoVo>(URL_SYS_STATISTICS_CPU)
export const r_sys_statistics_memory = () => request.get<MemoryInfoVo>(URL_SYS_STATISTICS_MEMORY) export const r_sys_statistics_storage = () => request.get<StorageInfoVo>(URL_SYS_STATISTICS_STORAGE)
export const r_sys_statistics_jvm = () => request.get<MemoryInfoVo>(URL_SYS_STATISTICS_JVM)