1
- import React , { useCallback , useState } from 'react' ;
1
+ import React , { useCallback , useRef , useState } from 'react' ;
2
2
import Chart , { ChartConfiguration } from 'chart.js' ;
3
3
import { ServerContext } from '@/state/server' ;
4
- import { bytesToMegabytes } from '@/helpers' ;
5
4
import merge from 'deepmerge' ;
6
5
import 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' ;
8
7
import tw from 'twin.macro' ;
9
8
import { SocketEvent } from '@/components/server/events' ;
10
9
import useWebsocketEvent from '@/plugins/useWebsocketEvent' ;
11
10
12
- const chartDefaults = ( ticks ?: Chart . TickOptions | undefined ) : ChartConfiguration => ( {
11
+ const chartDefaults = ( ticks ?: Chart . TickOptions ) : ChartConfiguration => ( {
13
12
type : 'line' ,
14
13
options : {
15
14
legend : {
@@ -69,38 +68,43 @@ const chartDefaults = (ticks?: Chart.TickOptions | undefined): ChartConfiguratio
69
68
} ,
70
69
} ) ;
71
70
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 ] ;
75
72
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 > ( ) ;
78
78
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 ) ) ;
83
83
84
- setMemory (
85
- new Chart ( node . getContext ( '2d' ) ! , chartDefaults ( {
86
- callback : ( value ) => `${ value } Mb ` ,
87
- suggestedMax : limits . memory ,
88
- } ) ) ,
89
- ) ;
84
+ setChart ( chart ) ;
90
85
} , [ ] ) ;
91
86
92
- const cpuRef = useCallback < ( node : HTMLCanvasElement | null ) => void > ( node => {
93
- if ( ! node ) {
94
- return ;
95
- }
87
+ return [ ref , chart ] ;
88
+ } ;
96
89
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 ` } ) ;
104
108
105
109
useWebsocketEvent ( SocketEvent . STATS , ( data : string ) => {
106
110
let stats : any = { } ;
@@ -110,54 +114,57 @@ export default () => {
110
114
return ;
111
115
}
112
116
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 ) ;
118
121
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 } ;
130
123
} ) ;
131
124
132
125
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 >
161
168
</ div >
162
169
) ;
163
170
} ;
0 commit comments