@ -1,3 +1,5 @@
/ / @ f l o w
/ / C o p y r i g h t 2 0 1 7 T h e g o - e t h e r e u m A u t h o r s
/ / T h i s f i l e i s p a r t o f t h e g o - e t h e r e u m l i b r a r y .
/ /
@ -15,155 +17,183 @@
/ / a l o n g w i t h t h e g o - e t h e r e u m l i b r a r y . I f n o t , s e e < h t t p : / / w w w . g n u . o r g / l i c e n s e s / > .
import React , { Component } from 'react' ;
import PropTypes from 'prop-types' ;
import { withStyles } from 'material-ui/styles' ;
import SideBar from './SideBar.jsx' ;
import Header from './Header.jsx' ;
import Main from "./Main.jsx" ;
import { isNullOrUndefined , LIMIT , TAGS , DATA _KEYS , } from "./Common.jsx" ;
import withStyles from 'material-ui/styles/withStyles' ;
import { lensPath , view , set } from 'ramda' ;
import Header from './Header' ;
import Body from './Body' ;
import { MENU , SAMPLE } from './Common' ;
import type { Message , HomeMessage , LogsMessage , Chart } from '../types/message' ;
import type { Content } from '../types/content' ;
/ / S t y l e s f o r t h e D a s h b o a r d c o m p o n e n t .
/ / a p p e n d e r a p p e n d s a n a r r a y ( A ) t o t h e e n d o f a n o t h e r a r r a y ( B ) i n t h e s t a t e .
/ / l e n s i s t h e p a t h o f B i n t h e s t a t e , s a m p l e s i s A , a n d l i m i t i s t h e m a x i m u m s i z e o f t h e c h a n g e d a r r a y .
/ /
/ / a p p e n d e r r e t r i e v e s a f u n c t i o n , w h i c h o v e r r i d e s t h e s t a t e ' s v a l u e a t l e n s , a n d r e t u r n s w i t h t h e o v e r r i d d e n s t a t e .
const appender = ( lens , samples , limit ) => ( state ) => {
const newSamples = [
... view ( lens , state ) , / / r e t r i e v e s a s p e c i f i c v a l u e o f t h e s t a t e a t t h e g i v e n p a t h ( l e n s ) .
... samples ,
] ;
/ / s e t i s a f u n c t i o n o f r a m d a . j s , w h i c h n e e d s t h e p a t h , t h e n e w v a l u e , t h e o r i g i n a l s t a t e , a n d r e t r i e v e s
/ / t h e a l t e r e d s t a t e .
return set (
lens ,
newSamples . slice ( newSamples . length > limit ? newSamples . length - limit : 0 ) ,
state
) ;
} ;
/ / L e n s e s f o r s p e c i f i c d a t a f i e l d s i n t h e s t a t e , u s e d f o r a c l e a r e r d e e p u p d a t e .
/ / N O T E : T h i s s o l u t i o n w i l l b e c h a n g e d v e r y l i k e l y .
const memoryLens = lensPath ( [ 'content' , 'home' , 'memory' ] ) ;
const trafficLens = lensPath ( [ 'content' , 'home' , 'traffic' ] ) ;
const logLens = lensPath ( [ 'content' , 'logs' , 'log' ] ) ;
/ / s t y l e s r e t r i e v e s t h e s t y l e s f o r t h e D a s h b o a r d c o m p o n e n t .
const styles = theme => ( {
appFrame : {
position : 'relative' ,
display : 'flex' ,
width : '100%' ,
height : '100%' ,
background : theme . palette . background . default ,
} ,
dashboard : {
display : 'flex' ,
flexFlow : 'column' ,
width : '100%' ,
height : '100%' ,
background : theme . palette . background . default ,
zIndex : 1 ,
overflow : 'hidden' ,
} ,
} ) ;
/ / D a s h b o a r d i s t h e m a i n c o m p o n e n t , w h i c h r e n d e r s t h e w h o l e p a g e , m a k e s c o n n e c t i o n w i t h t h e s e r v e r a n d l i s t e n s f o r m e s s a g e s .
/ / W h e n t h e r e i s a n i n c o m i n g m e s s a g e , u p d a t e s t h e p a g e ' s c o n t e n t c o r r e s p o n d i n g l y .
class Dashboard extends Component {
constructor ( props ) {
super ( props ) ;
this . state = {
active : TAGS . home . id , / / a c t i v e m e n u
sideBar : true , / / t r u e i f t h e s i d e b a r i s o p e n e d
memory : [ ] ,
traffic : [ ] ,
logs : [ ] ,
shouldUpdate : { } ,
} ;
}
/ / c o m p o n e n t D i d M o u n t i n i t i a t e s t h e e s t a b l i s h m e n t o f t h e f i r s t w e b s o c k e t c o n n e c t i o n a f t e r t h e c o m p o n e n t i s r e n d e r e d .
componentDidMount ( ) {
this . reconnect ( ) ;
}
/ / r e c o n n e c t e s t a b l i s h e s a w e b s o c k e t c o n n e c t i o n w i t h t h e s e r v e r , l i s t e n s f o r i n c o m i n g m e s s a g e s
/ / a n d t r i e s t o r e c o n n e c t o n c o n n e c t i o n l o s s .
reconnect = ( ) => {
const server = new WebSocket ( ( ( window . location . protocol === "https:" ) ? "wss://" : "ws://" ) + window . location . host + "/api" ) ;
server . onmessage = event => {
const msg = JSON . parse ( event . data ) ;
if ( isNullOrUndefined ( msg ) ) {
return ;
}
this . update ( msg ) ;
} ;
server . onclose = ( ) => {
setTimeout ( this . reconnect , 3000 ) ;
} ;
} ;
/ / u p d a t e a n a l y z e s t h e i n c o m i n g m e s s a g e , a n d u p d a t e s t h e c h a r t s ' c o n t e n t c o r r e s p o n d i n g l y .
update = msg => {
console . log ( msg ) ;
this . setState ( prevState => {
let newState = [ ] ;
newState . shouldUpdate = { } ;
const insert = ( key , values , limit ) => {
newState [ key ] = [ ... prevState [ key ] , ... values ] ;
while ( newState [ key ] . length > limit ) {
newState [ key ] . shift ( ) ;
}
newState . shouldUpdate [ key ] = true ;
} ;
/ / ( R e ) i n i t i a l i z e t h e s t a t e w i t h t h e p a s t d a t a .
if ( ! isNullOrUndefined ( msg . history ) ) {
const memory = DATA _KEYS . memory ;
const traffic = DATA _KEYS . traffic ;
newState [ memory ] = [ ] ;
newState [ traffic ] = [ ] ;
if ( ! isNullOrUndefined ( msg . history . memorySamples ) ) {
newState [ memory ] = msg . history . memorySamples . map ( elem => isNullOrUndefined ( elem . value ) ? 0 : elem . value ) ;
while ( newState [ memory ] . length > LIMIT . memory ) {
newState [ memory ] . shift ( ) ;
}
newState . shouldUpdate [ memory ] = true ;
}
if ( ! isNullOrUndefined ( msg . history . trafficSamples ) ) {
newState [ traffic ] = msg . history . trafficSamples . map ( elem => isNullOrUndefined ( elem . value ) ? 0 : elem . value ) ;
while ( newState [ traffic ] . length > LIMIT . traffic ) {
newState [ traffic ] . shift ( ) ;
}
newState . shouldUpdate [ traffic ] = true ;
}
}
/ / I n s e r t t h e n e w d a t a s a m p l e s .
if ( ! isNullOrUndefined ( msg . memory ) ) {
insert ( DATA _KEYS . memory , [ isNullOrUndefined ( msg . memory . value ) ? 0 : msg . memory . value ] , LIMIT . memory ) ;
}
if ( ! isNullOrUndefined ( msg . traffic ) ) {
insert ( DATA _KEYS . traffic , [ isNullOrUndefined ( msg . traffic . value ) ? 0 : msg . traffic . value ] , LIMIT . traffic ) ;
}
if ( ! isNullOrUndefined ( msg . log ) ) {
insert ( DATA _KEYS . logs , [ msg . log ] , LIMIT . log ) ;
}
return newState ;
} ) ;
} ;
/ / T h e c h a n g e o f t h e a c t i v e l a b e l o n t h e S i d e B a r c o m p o n e n t w i l l t r i g g e r a n e w r e n d e r i n t h e M a i n c o m p o n e n t .
changeContent = active => {
this . setState ( prevState => prevState . active !== active ? { active : active } : { } ) ;
} ;
openSideBar = ( ) => {
this . setState ( { sideBar : true } ) ;
} ;
closeSideBar = ( ) => {
this . setState ( { sideBar : false } ) ;
} ;
render ( ) {
/ / T h e c l a s s e s p r o p e r t y i s i n j e c t e d b y w i t h S t y l e s ( ) .
const { classes } = this . props ;
return (
< div className = { classes . appFrame } >
< Header
opened = { this . state . sideBar }
open = { this . openSideBar }
/ >
< SideBar
opened = { this . state . sideBar }
close = { this . closeSideBar }
changeContent = { this . changeContent }
/ >
< Main
opened = { this . state . sideBar }
active = { this . state . active }
memory = { this . state . memory }
traffic = { this . state . traffic }
logs = { this . state . logs }
shouldUpdate = { this . state . shouldUpdate }
/ >
< / div >
) ;
}
}
Dashboard . propTypes = {
classes : PropTypes . object . isRequired ,
export type Props = {
classes : Object ,
} ;
type State = {
active : string , / / a c t i v e m e n u
sideBar : boolean , / / t r u e i f t h e s i d e b a r i s o p e n e d
content : $Shape < Content > , / / t h e v i s u a l i z e d d a t a
shouldUpdate : Set < string > / / l a b e l s f o r t h e c o m p o n e n t s , w h i c h n e e d t o r e r e n d e r b a s e d o n t h e i n c o m i n g m e s s a g e
} ;
/ / D a s h b o a r d i s t h e m a i n c o m p o n e n t , w h i c h r e n d e r s t h e w h o l e p a g e , m a k e s c o n n e c t i o n w i t h t h e s e r v e r a n d
/ / l i s t e n s f o r m e s s a g e s . W h e n t h e r e i s a n i n c o m i n g m e s s a g e , u p d a t e s t h e p a g e ' s c o n t e n t c o r r e s p o n d i n g l y .
class Dashboard extends Component < Props , State > {
constructor ( props : Props ) {
super ( props ) ;
this . state = {
active : MENU . get ( 'home' ) . id ,
sideBar : true ,
content : { home : { memory : [ ] , traffic : [ ] } , logs : { log : [ ] } } ,
shouldUpdate : new Set ( ) ,
} ;
}
/ / c o m p o n e n t D i d M o u n t i n i t i a t e s t h e e s t a b l i s h m e n t o f t h e f i r s t w e b s o c k e t c o n n e c t i o n a f t e r t h e c o m p o n e n t i s r e n d e r e d .
componentDidMount ( ) {
this . reconnect ( ) ;
}
/ / r e c o n n e c t e s t a b l i s h e s a w e b s o c k e t c o n n e c t i o n w i t h t h e s e r v e r , l i s t e n s f o r i n c o m i n g m e s s a g e s
/ / a n d t r i e s t o r e c o n n e c t o n c o n n e c t i o n l o s s .
reconnect = ( ) => {
this . setState ( {
content : { home : { memory : [ ] , traffic : [ ] } , logs : { log : [ ] } } ,
} ) ;
const server = new WebSocket ( ` ${ ( ( window . location . protocol === 'https:' ) ? 'wss://' : 'ws://' ) + window . location . host } /api ` ) ;
server . onmessage = ( event ) => {
const msg : Message = JSON . parse ( event . data ) ;
if ( ! msg ) {
return ;
}
this . update ( msg ) ;
} ;
server . onclose = ( ) => {
setTimeout ( this . reconnect , 3000 ) ;
} ;
} ;
/ / s a m p l e s r e t r i e v e s t h e r a w d a t a o f a c h a r t f i e l d f r o m t h e i n c o m i n g m e s s a g e .
samples = ( chart : Chart ) => {
let s = [ ] ;
if ( chart . history ) {
s = chart . history . map ( ( { value } ) => ( value || 0 ) ) ; / / t r a f f i c c o m e s w i t h o u t v a l u e a t t h e b e g i n n i n g
}
if ( chart . new ) {
s = [ ... s , chart . new . value || 0 ] ;
}
return s ;
} ;
/ / h a n d l e H o m e c h a n g e s t h e h o m e - m e n u r e l a t e d p a r t o f t h e s t a t e .
handleHome = ( home : HomeMessage ) => {
this . setState ( ( prevState ) => {
let newState = prevState ;
newState . shouldUpdate = new Set ( ) ;
if ( home . memory ) {
newState = appender ( memoryLens , this . samples ( home . memory ) , SAMPLE . get ( 'memory' ) . limit ) ( newState ) ;
newState . shouldUpdate . add ( 'memory' ) ;
}
if ( home . traffic ) {
newState = appender ( trafficLens , this . samples ( home . traffic ) , SAMPLE . get ( 'traffic' ) . limit ) ( newState ) ;
newState . shouldUpdate . add ( 'traffic' ) ;
}
return newState ;
} ) ;
} ;
/ / h a n d l e L o g s c h a n g e s t h e l o g s - m e n u r e l a t e d p a r t o f t h e s t a t e .
handleLogs = ( logs : LogsMessage ) => {
this . setState ( ( prevState ) => {
let newState = prevState ;
newState . shouldUpdate = new Set ( ) ;
if ( logs . log ) {
newState = appender ( logLens , [ logs . log ] , SAMPLE . get ( 'logs' ) . limit ) ( newState ) ;
newState . shouldUpdate . add ( 'logs' ) ;
}
return newState ;
} ) ;
} ;
/ / u p d a t e a n a l y z e s t h e i n c o m i n g m e s s a g e , a n d u p d a t e s t h e c h a r t s ' c o n t e n t c o r r e s p o n d i n g l y .
update = ( msg : Message ) => {
if ( msg . home ) {
this . handleHome ( msg . home ) ;
}
if ( msg . logs ) {
this . handleLogs ( msg . logs ) ;
}
} ;
/ / c h a n g e C o n t e n t s e t s t h e a c t i v e l a b e l , w h i c h i s u s e d a t t h e c o n t e n t r e n d e r i n g .
changeContent = ( newActive : string ) => {
this . setState ( prevState => ( prevState . active !== newActive ? { active : newActive } : { } ) ) ;
} ;
/ / o p e n S i d e B a r o p e n s t h e s i d e b a r .
openSideBar = ( ) => {
this . setState ( { sideBar : true } ) ;
} ;
/ / c l o s e S i d e B a r c l o s e s t h e s i d e b a r .
closeSideBar = ( ) => {
this . setState ( { sideBar : false } ) ;
} ;
render ( ) {
const { classes } = this . props ; / / T h e c l a s s e s p r o p e r t y i s i n j e c t e d b y w i t h S t y l e s ( ) .
return (
< div className = { classes . dashboard } >
< Header
opened = { this . state . sideBar }
openSideBar = { this . openSideBar }
closeSideBar = { this . closeSideBar }
/ >
< Body
opened = { this . state . sideBar }
changeContent = { this . changeContent }
active = { this . state . active }
content = { this . state . content }
shouldUpdate = { this . state . shouldUpdate }
/ >
< / div >
) ;
}
}
export default withStyles ( styles ) ( Dashboard ) ;