1- import React , { useCallback , useState } from 'react' ;
1+ import React , { useCallback , useRef , useState } from 'react' ;
22import Chart , { ChartConfiguration } from 'chart.js' ;
33import { ServerContext } from '@/state/server' ;
4- import { bytesToMegabytes } from '@/helpers' ;
54import merge from 'deepmerge' ;
65import TitledGreyBox from '@/components/elements/TitledGreyBox' ;
7- import { faMemory , faMicrochip } from '@fortawesome/free-solid-svg-icons' ;
6+ import { faEthernet , faMemory , faMicrochip } from '@fortawesome/free-solid-svg-icons' ;
87import tw from 'twin.macro' ;
98import { SocketEvent } from '@/components/server/events' ;
109import useWebsocketEvent from '@/plugins/useWebsocketEvent' ;
1110
12- const chartDefaults = ( ticks ?: Chart . TickOptions | undefined ) : ChartConfiguration => ( {
11+ const chartDefaults = ( ticks ?: Chart . TickOptions ) : ChartConfiguration => ( {
1312 type : 'line' ,
1413 options : {
1514 legend : {
@@ -69,38 +68,43 @@ const chartDefaults = (ticks?: Chart.TickOptions | undefined): ChartConfiguratio
6968 } ,
7069} ) ;
7170
72- export default ( ) => {
73- const status = ServerContext . useStoreState ( state => state . status . value ) ;
74- const limits = ServerContext . useStoreState ( state => state . server . data ! . limits ) ;
71+ type ChartState = [ ( node : HTMLCanvasElement | null ) => void , Chart | undefined ] ;
7572
76- const [ memory , setMemory ] = useState < Chart > ( ) ;
77- const [ cpu , setCpu ] = useState < Chart > ( ) ;
73+ /**
74+ * Creates an element ref and a chart instance.
75+ */
76+ const useChart = ( options ?: Chart . TickOptions ) : ChartState => {
77+ const [ chart , setChart ] = useState < Chart > ( ) ;
7878
79- const memoryRef = useCallback < ( node : HTMLCanvasElement | null ) => void > ( node => {
80- if ( ! node ) {
81- return ;
82- }
79+ const ref = useCallback < ( node : HTMLCanvasElement | null ) => void > ( node => {
80+ if ( ! node ) return ;
81+
82+ const chart = new Chart ( node . getContext ( '2d' ) ! , chartDefaults ( options ) ) ;
8383
84- setMemory (
85- new Chart ( node . getContext ( '2d' ) ! , chartDefaults ( {
86- callback : ( value ) => `${ value } Mb ` ,
87- suggestedMax : limits . memory ,
88- } ) ) ,
89- ) ;
84+ setChart ( chart ) ;
9085 } , [ ] ) ;
9186
92- const cpuRef = useCallback < ( node : HTMLCanvasElement | null ) => void > ( node => {
93- if ( ! node ) {
94- return ;
95- }
87+ return [ ref , chart ] ;
88+ } ;
9689
97- setCpu (
98- new Chart ( node . getContext ( '2d' ) ! , chartDefaults ( {
99- callback : ( value ) => `${ value } % ` ,
100- suggestedMax : limits . cpu ,
101- } ) ) ,
102- ) ;
103- } , [ ] ) ;
90+ const updateChartDataset = ( chart : Chart | null | undefined , value : Chart . ChartPoint & number ) : void => {
91+ if ( ! chart || ! chart . data ?. datasets ) return ;
92+
93+ const data = chart . data . datasets [ 0 ] . data ! ;
94+ data . push ( value ) ;
95+ data . shift ( ) ;
96+ chart . update ( { lazy : true } ) ;
97+ } ;
98+
99+ export default ( ) => {
100+ const status = ServerContext . useStoreState ( state => state . status . value ) ;
101+ const limits = ServerContext . useStoreState ( state => state . server . data ! . limits ) ;
102+
103+ const previous = useRef < Record < 'tx' | 'rx' , number > > ( { tx : - 1 , rx : - 1 } ) ;
104+ const [ cpuRef , cpu ] = useChart ( { callback : ( value ) => `${ value } % ` , suggestedMax : limits . cpu } ) ;
105+ const [ memoryRef , memory ] = useChart ( { callback : ( value ) => `${ value } Mb ` , suggestedMax : limits . memory } ) ;
106+ const [ txRef , tx ] = useChart ( { callback : ( value ) => `${ value } Kb/s ` } ) ;
107+ const [ rxRef , rx ] = useChart ( { callback : ( value ) => `${ value } Kb/s ` } ) ;
104108
105109 useWebsocketEvent ( SocketEvent . STATS , ( data : string ) => {
106110 let stats : any = { } ;
@@ -110,54 +114,57 @@ export default () => {
110114 return ;
111115 }
112116
113- if ( memory && memory . data . datasets ) {
114- const data = memory . data . datasets [ 0 ] . data ! ;
115-
116- data . push ( bytesToMegabytes ( stats . memory_bytes ) ) ;
117- data . shift ( ) ;
117+ updateChartDataset ( cpu , stats . cpu_absolute ) ;
118+ updateChartDataset ( memory , Math . floor ( stats . memory_bytes / 1024 / 1024 ) ) ;
119+ updateChartDataset ( tx , previous . current . tx < 0 ? 0 : Math . max ( 0 , stats . network . tx_bytes - previous . current . tx ) / 1024 ) ;
120+ updateChartDataset ( rx , previous . current . rx < 0 ? 0 : Math . max ( 0 , stats . network . rx_bytes - previous . current . rx ) / 1024 ) ;
118121
119- memory . update ( { lazy : true } ) ;
120- }
121-
122- if ( cpu && cpu . data . datasets ) {
123- const data = cpu . data . datasets [ 0 ] . data ! ;
124-
125- data . push ( stats . cpu_absolute ) ;
126- data . shift ( ) ;
127-
128- cpu . update ( { lazy : true } ) ;
129- }
122+ previous . current = { tx : stats . network . tx_bytes , rx : stats . network . rx_bytes } ;
130123 } ) ;
131124
132125 return (
133- < div css = { tw `flex flex-wrap mt-4` } >
134- < div css = { tw `w-full sm:w-1/2` } >
135- < TitledGreyBox title = { 'Memory usage' } icon = { faMemory } css = { tw `mr-0 sm:mr-4` } >
136- { status !== 'offline' ?
137- < canvas
138- id = { 'memory_chart' }
139- ref = { memoryRef }
140- aria-label = { 'Server Memory Usage Graph' }
141- role = { 'img' }
142- />
143- :
144- < p css = { tw `text-xs text-neutral-400 text-center p-3` } >
145- Server is offline.
146- </ p >
147- }
148- </ TitledGreyBox >
149- </ div >
150- < div css = { tw `w-full sm:w-1/2 mt-4 sm:mt-0` } >
151- < TitledGreyBox title = { 'CPU usage' } icon = { faMicrochip } css = { tw `ml-0 sm:ml-4` } >
152- { status !== 'offline' ?
153- < canvas id = { 'cpu_chart' } ref = { cpuRef } aria-label = { 'Server CPU Usage Graph' } role = { 'img' } />
154- :
155- < p css = { tw `text-xs text-neutral-400 text-center p-3` } >
156- Server is offline.
157- </ p >
158- }
159- </ TitledGreyBox >
160- </ div >
126+ < div css = { tw `mt-4 grid grid-cols-1 sm:grid-cols-2 gap-4` } >
127+ < TitledGreyBox title = { 'Memory usage' } icon = { faMemory } >
128+ { status !== 'offline' ?
129+ < canvas
130+ id = { 'memory_chart' }
131+ ref = { memoryRef }
132+ aria-label = { 'Server Memory Usage Graph' }
133+ role = { 'img' }
134+ />
135+ :
136+ < p css = { tw `text-xs text-neutral-400 text-center p-3` } >
137+ Server is offline.
138+ </ p >
139+ }
140+ </ TitledGreyBox >
141+ < TitledGreyBox title = { 'CPU usage' } icon = { faMicrochip } >
142+ { status !== 'offline' ?
143+ < canvas id = { 'cpu_chart' } ref = { cpuRef } aria-label = { 'Server CPU Usage Graph' } role = { 'img' } />
144+ :
145+ < p css = { tw `text-xs text-neutral-400 text-center p-3` } >
146+ Server is offline.
147+ </ p >
148+ }
149+ </ TitledGreyBox >
150+ < TitledGreyBox title = { 'Inbound Data' } icon = { faEthernet } >
151+ { status !== 'offline' ?
152+ < canvas id = { 'rx_chart' } ref = { rxRef } aria-label = { 'Server Inbound Data' } role = { 'img' } />
153+ :
154+ < p css = { tw `text-xs text-neutral-400 text-center p-3` } >
155+ Server is offline.
156+ </ p >
157+ }
158+ </ TitledGreyBox >
159+ < TitledGreyBox title = { 'Outbound Data' } icon = { faEthernet } >
160+ { status !== 'offline' ?
161+ < canvas id = { 'tx_chart' } ref = { txRef } aria-label = { 'Server Outbound Data' } role = { 'img' } />
162+ :
163+ < p css = { tw `text-xs text-neutral-400 text-center p-3` } >
164+ Server is offline.
165+ </ p >
166+ }
167+ </ TitledGreyBox >
161168 </ div >
162169 ) ;
163170} ;
0 commit comments