blob: 32a19fcf7e70674ac038bfb1ba6585e2491872b0 [file] [log] [blame]
Andrea Campanella362b7d32018-12-11 18:57:12 +01001import green from '@material-ui/core/colors/green'
2import red from '@material-ui/core/colors/red'
3import {
4 Button,
5 FormControl,
6 Grid,
7 Icon,
8 InputLabel,
9 MenuItem,
10 Select,
11 Snackbar,
12 SnackbarContent,
13} from '@material-ui/core/es/index'
14import SendIcon from '@material-ui/icons/Send'
15import DeleteIcon from '@material-ui/icons/Delete'
16import withStyles from '@material-ui/core/es/styles/withStyles'
17import React, {
18 Component,
19 createRef,
20} from 'react';
21import {
22 Network,
23 DataSet,
24} from 'vis'
25import onos from './onos.png'
26import './App.css';
27import {
Hiroki Okui458da6d2019-02-15 10:09:21 -080028 createClientSideConnectivityService,
29 createLineSideConnectivityService,
Andrea Campanella362b7d32018-12-11 18:57:12 +010030 deleteConnectivityServices,
31} from './resources/dcs'
32import { getResources } from './resources/getResources'
33import { range } from 'lodash'
34
35
36const INTERVAL = 10000
37
38const GRAPH_OPTION = {
39 // edges: {
40 // smooth: true,
41 // },
42}
43
44const SPACE = {
45 v: 15,
46 h: 100,
47}
48
49const DEBUG = false
50
51const styles = theme => ( {
52 root: {
53 flexGrow: 1,
54 },
55 main: {
56 marginTop: '20px',
57 padding: '10px',
58 border: '0.5px solid #666666',
59 },
60 form: {
61 minWidth: 120,
62 display: 'flex',
63 flexWrap: 'wrap',
64 },
65 button: {
66 margin: theme.spacing.unit,
67 },
68 leftIcon: {
69 marginRight: theme.spacing.unit,
70 },
71 rightIcon: {
72 marginLeft: theme.spacing.unit,
73 },
74} )
75
76
77class App extends Component {
78
79 constructor( props ) {
80 super()
81 this.state = {
82 connectivityServices: [],
83 devices: [],
84 network: {},
85 request: {
86 left: '',
87 right: '',
88 },
89 message: '',
90 messageColor: green[ 600 ],
91 open: false,
92 }
93 this.appRefList = range( 8 ).map( () => createRef() )
94 }
95
96 componentDidMount = () => {
97 this.updateResource()
98 setTimeout( this.updateResource, 100 )
99 setInterval( this.updateResource, INTERVAL )
100 }
101
102 updateResource = () => {
103 getResources().then( ( [ devices, connectivityServices ] ) => {
Hiroki Okui458da6d2019-02-15 10:09:21 -0800104 console.log( 'devices', devices )
105 console.log( 'connectivity services', connectivityServices )
Andrea Campanella362b7d32018-12-11 18:57:12 +0100106 const portMap = this.getPortMap( devices )
107 const network = Object.values( portMap ).map( this.setupGraph )
108 this.setState( {
109 devices,
110 network,
111 connectivityServices,
112 } )
113 } )
114 }
115
116 getPortMap = ( devices ) => {
117 const portMap = {}
118 range( 8 ).forEach( idx => portMap[ idx ] = [] )
119
120 devices.forEach( ( device, deviceIdx ) => {
121 device.ports.forEach( port => {
Hiroki Okui458da6d2019-02-15 10:09:21 -0800122 const portNum = parseInt(port.port)
Andrea Campanella362b7d32018-12-11 18:57:12 +0100123 const lineNum = Math.floor( ( portNum - 1 ) % 100 / 2 )
124
125 port.id = `${port.element}/${portNum}`
126 port.portNum = portNum
127 port.isClient = Math.floor( portNum / 100 ) === 1
128 port.isLeft = deviceIdx === 0
129 port.color = !port.isClient ? '#888888' : deviceIdx === 0 ? '#DD8800' : '#0088DD'
130 port.isEven = port.portNum % 2 === 0
131
132 portMap[ lineNum ].push( port )
133 } )
134 } )
135
136 return portMap
137 }
138
139 getAssociatedConnectivity = ( uuid ) => {
140 let oppositePortUuid = null
141 let connectivitySrv = null
142 this.state.connectivityServices.forEach( srv => {
143 let isTarget = false
144 srv[ 'end-point' ].forEach( ep => {
145 const epUuid = ep[ 'service-interface-point' ][ 'service-interface-point-uuid' ]
146 if ( uuid === epUuid ) {
147 connectivitySrv = srv
148 isTarget = true
149 }
150 } )
151 if ( isTarget ) {
152 oppositePortUuid = srv[ 'end-point' ].filter( ep => {
153 const epUuid = ep[ 'service-interface-point' ][ 'service-interface-point-uuid' ]
154 return epUuid !== uuid
155 } )[ 0 ][ 'service-interface-point' ][ 'service-interface-point-uuid' ]
156 }
157 } )
158 return [ connectivitySrv, oppositePortUuid ]
159 }
160
161 checkRequest = ( leftUuid, rightUuid ) => {
162 const portMap = this.getPortMap( this.state.devices )
163
164 const getPosition = ( uuid ) => {
165 let position = null
166 Object.entries( portMap ).forEach( ( [ idx, ports ] ) => {
167 const exist = ports.filter( port => port.sipId === uuid ).length === 1
168 if ( exist ) {
169 position = idx
170 }
171 } )
172 if ( position === null ) {
173 throw Error( 'Port not found' )
174 }
175 return position
176 }
177
178 if ( getPosition( leftUuid ) !== getPosition( rightUuid ) ) {
179 throw Error( 'Invalid request' )
180 }
181 }
182
183 setupGraph = ( ports, idx ) => {
184
185 // predefined port
186 const nodes = ports
187 .filter( port => port.isClient || !port.isEven )
188 .map( port => {
189 const [ x, y ] = getPosition( port )
190 return {
191 id: port.id,
192 label: port.isClient ? port.portNum : '',
193 x,
194 y,
195 shape: 'ellipse',
196 size: 20,
197 color: port.color,
198 physics: false,
199 // font: '20px',
200 }
201 } )
202
203 // predefined line
204 const edgeMap = {}
205 ports.forEach( port => {
206 if ( port.isClient ) {
207 return
208 }
209 edgeMap[ port.portNum ] = edgeMap[ port.portNum ] || {}
210 if ( port.isLeft ) {
211 edgeMap[ port.portNum ].from = port.id
212 } else {
213 edgeMap[ port.portNum ].to = port.id
214 }
215 edgeMap[ port.portNum ].label = 'optical line'
216 } )
217 const externalEdges = Object.values( edgeMap )
218
219 // existing connectivity service
220 const internalEdges = []
221 ports.forEach( port => {
222 if ( !port.isClient ) {
223 return
224 }
225 if ( !port.isLeft ) {
226 return
227 }
228
229 const [ connectivitySrv, oppositePortUuid ] = this.getAssociatedConnectivity( port.sipId )
230 if ( !connectivitySrv ) {
231 return
232 }
233 const oppositePort = ports.filter( port => port.sipId === oppositePortUuid )[ 0 ]
234
Andrea Campanella362b7d32018-12-11 18:57:12 +0100235 internalEdges.push( {
236 from: port.id,
237 to: ports.filter( _port => !_port.isClient && _port.element === port.element )[ 0 ].id,
238 } )
239
240 internalEdges.push( {
241 from: oppositePort.id,
242 to: ports.filter( _port => !_port.isClient && _port.element === oppositePort.element )[ 0 ].id,
243 } )
244 } )
245
246 const edges = externalEdges.concat( internalEdges )
247
248 const data = {
249 nodes: new DataSet( nodes ),
250 edges: new DataSet( edges ),
251 }
252 return new Network( this.appRefList[ idx ].current, data, GRAPH_OPTION )
253 }
254
Hiroki Okui458da6d2019-02-15 10:09:21 -0800255 deleteConnectivityServicePromise = (leftSipId, rightSipId) => {
256 const [ connectivitySrv, oppositePortUuid ] = this.getAssociatedConnectivity( leftSipId )
257 if ( oppositePortUuid !== rightSipId ) {
258 const message = 'Connectivity Service not found.'
259 this.setState( {
260 message,
261 } )
262 return Promise.reject(message)
263 }
264 if ( oppositePortUuid !== rightSipId ) {
265 const message = 'Connectivity Service and associated SIP are inconsistent.'
266 this.setState( {
267 message,
268 } )
269 return Promise.reject(message)
270 }
271 return deleteConnectivityServices( connectivitySrv.uuid )
272 }
273
274
275
Andrea Campanella362b7d32018-12-11 18:57:12 +0100276 handleChange = ( ev ) => {
277 if ( ev.target.name === 'left' ) {
278 const request = Object.assign( {}, this.state.request, {
279 left: ev.target.value,
280 } )
281 this.setState( {
282 request,
283 } )
284 } else {
285 const request = Object.assign( {}, this.state.request, {
286 right: ev.target.value,
287 } )
288 this.setState( {
289 request,
290 } )
291 }
292 }
293
294 handleCreate = () => {
295 const { left, right } = this.state.request
296 try {
Hiroki Okui458da6d2019-02-15 10:09:21 -0800297 this.checkRequest( left.sipId, right.sipId )
Andrea Campanella362b7d32018-12-11 18:57:12 +0100298 }catch(err){
299 this.setState( {
300 message: err.message,
301 messageColor: red[ 600 ],
302 } )
303 return
304 }
305
Hiroki Okui458da6d2019-02-15 10:09:21 -0800306 createClientSideConnectivityService( left.sipId, right.sipId )
307 .then( createLineSideConnectivityService.bind( null,
308 left.associatedLinePort.mediaSipId,
309 right.associatedLinePort.mediaSipId )
310 )
Andrea Campanella362b7d32018-12-11 18:57:12 +0100311 .then( json => {
312 const message = DEBUG ? JSON.stringify( json ) : 'Create ConnectivityService completed.'
313 this.setState( {
314 message,
315 messageColor: green[ 600 ],
316 } )
317 setTimeout( this.updateResource, 1000 )
318 } )
319 .catch( err => {
320 console.error( err )
321 this.setState( {
322 message: 'Error',
323 messageColor: red[ 600 ],
324 } )
325 } )
326 }
327
328 handleDelete = () => {
329 const { left, right } = this.state.request
330 try {
Hiroki Okui458da6d2019-02-15 10:09:21 -0800331 this.checkRequest( left.sipId, right.sipId )
Andrea Campanella362b7d32018-12-11 18:57:12 +0100332 }catch(err){
333 this.setState( {
334 message: err.message,
335 messageColor: red[ 600 ],
336 } )
337 return
338 }
339
Hiroki Okui458da6d2019-02-15 10:09:21 -0800340 this.deleteConnectivityServicePromise(left.sipId, right.sipId)
341 .then(this.deleteConnectivityServicePromise(left.associatedLinePort.mediaSipId, right.associatedLinePort.mediaSipId))
Andrea Campanella362b7d32018-12-11 18:57:12 +0100342 .then( json => {
343 const message = 'Delete ConnectivityService completed.'
344 this.setState( {
345 message,
346 messageColor: green[ 600 ],
347 } )
348 setTimeout( this.updateResource, 1000 )
349 } )
350 .catch( err => {
351 console.error( err )
352 this.setState( {
353 message: 'Error',
354 messageColor: red[ 600 ],
355 } )
356 } )
357 }
358
359 handleSnackClose = () => {
360 this.setState( {
361 message: '',
362 } )
363 }
364
365
366 render() {
367
368 const { classes } = this.props
369
370 const { devices } = this.state
371
372 const leftSipPorts = devices[ 0 ] ? devices[ 0 ].ports.filter( port => port.sipId ) : []
373 const rightSipPorts = devices[ 1 ] ? devices[ 1 ].ports.filter( port => port.sipId ) : []
374
375 return (
376 <div className="App">
377 <header className="App-header">
378 <img src={ onos } className="App-logo" alt="logo"/>
379 <p>
380 ODTN sample app
381 </p>
382 </header>
383 <Grid container className={ classes.root } spacing={ 24 }>
384 <Grid item xs={ 2 }/>
385 <Grid item container xs={ 8 } spacing={ 24 }>
386 <Grid item xs={ 4 }>
387 <form>
388 <FormControl className={ classes.form }>
389 <InputLabel>Client Port 1</InputLabel>
390 <Select value={ this.state.request.left }
391 onChange={ this.handleChange }
392 inputProps={ {
393 name: 'left',
394 id: 'left',
395 } }>
396 {
397 leftSipPorts.map( port => (
Hiroki Okui458da6d2019-02-15 10:09:21 -0800398 <MenuItem key={ port.id } value={ port }>{ port.id }</MenuItem>
Andrea Campanella362b7d32018-12-11 18:57:12 +0100399 ) )
400 }
401 </Select>
402 </FormControl>
403 </form>
404 </Grid>
405 <Grid item xs={ 4 }>
406 <form>
407 <FormControl className={ classes.form }>
408 <InputLabel>Client Port 2</InputLabel>
409 <Select value={ this.state.request.right }
410 onChange={ this.handleChange }
411 inputProps={ {
412 name: 'right',
413 id: 'right',
414 } }>
415 {
416 rightSipPorts.map( port => (
Hiroki Okui458da6d2019-02-15 10:09:21 -0800417 <MenuItem key={ port.id } value={ port }>{ port.id }</MenuItem>
Andrea Campanella362b7d32018-12-11 18:57:12 +0100418 ) )
419 }
420 </Select>
421 </FormControl>
422 </form>
423 </Grid>
424
425 <Grid item xs={ 2 }>
426 <Button variant="contained" className={ classes.button } onClick={ this.handleCreate }>
427 Create
428 { /* This Button uses a Font Icon, see the installation instructions in the docs. */ }
429 <SendIcon className={ classes.rightIcon }/>
430 </Button>
431 </Grid>
432
433 <Grid item xs={ 2 }>
434 <Button variant="contained" className={ classes.button } onClick={ this.handleDelete }>
435 Delete
436 { /* This Button uses a Font Icon, see the installation instructions in the docs. */ }
437 <DeleteIcon className={ classes.rightIcon }/>
438 </Button>
439 </Grid>
440
441 <Grid item className={ classes.main } container xs={ 12 } spacing={ 24 }>
442 <Grid item xs={ 1 }/>
443 <Grid item xs={ 5 }>
444 <p>Cassini 1</p>
445 <p>{ devices[ 0 ] ? devices[ 0 ].id : '' }</p>
446 </Grid>
447 <Grid item xs={ 5 }>
448 <p>Cassini 2</p>
449 <p>{ devices[ 1 ] ? devices[ 1 ].id : '' }</p>
450 </Grid>
451 <Grid item xs={ 1 }/>
452 {
453 this.appRefList.map( ( appRef, idx ) => (
454 <Grid item xs={ 12 }>
455 <div className="odtn-graph" key={ `vis${idx}` } ref={ appRef }/>
456 </Grid>
457 ) )
458 }
459 </Grid>
460
461 </Grid>
462
463 <Grid item xs={ 2 }/>
464 </Grid>
465 <Snackbar
466 anchorOrigin={ {
467 vertical: 'top',
468 horizontal: 'right',
469 } }
470 open={ Boolean( this.state.message ) }
471 autoHideDuration={ 4000 }
472 onClose={ this.handleSnackClose }
473 ContentProps={ {
474 'aria-describedby': 'message-id',
475 } }
476 >
477 <SnackbarContent
478 style={ { backgroundColor: this.state.messageColor } }
479 message={ this.state.message }
480 />
481 </Snackbar>
482 </div>
483
484 );
485 }
486}
487
488export default withStyles( styles )( App );
489
490
491function getPosition( port ) {
492 const { v, h } = SPACE
493 if ( port.isClient ) {
494 if ( port.isLeft ) {
495 if ( port.isEven ) {
496 return [ h * -3, v * 1.5 ]
497 } else {
498 return [ h * -3, v * -1.5 ]
499 }
500 } else {
501 if ( port.isEven ) {
502 return [ h * 3, v * 1.5 ]
503 } else {
504 return [ h * 3, v * -1.5 ]
505 }
506 }
507
508 } else {
509 if ( port.isLeft ) {
510 if ( port.isEven ) {
511 return [ h * -1, v ]
512 } else {
513 return [ h * -1, v * -1 ]
514 }
515 }
516 else {
517 if ( port.isEven ) {
518 return [ h, v ]
519 } else {
520 return [ h, v * -1 ]
521 }
522 }
523 }
524}