4 * Copyright 2016 RIFT.IO Inc
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
20 // rw.js will no longer be necessary when Angular dependency no longer exists
22 * reset values in an array, useful when an array instance is
23 * being observed for changes and simply setting array reference
24 * to a new array instance is not a great option.
28 * setValues(x, ['c', 'd'])
33 import _isNumber
from 'lodash/isNumber';
34 import _each
from 'lodash/each';
35 import _flatten
from 'lodash/flatten';
36 import _union
from 'lodash/union';
39 // Angular specific for now but can be modified in one location if need ever be
40 BaseController: function() {
43 // handles factories with detach/cancel methods and listeners with cancel method
45 this.$scope
.$on('$stateChangeStart', function() {
46 var properties
= Object
.getOwnPropertyNames(self
);
48 properties
.forEach(function(key
) {
50 var propertyValue
= self
[key
];
53 if (Array
.isArray(propertyValue
)) {
54 propertyValue
.forEach(function(item
) {
55 if (item
.off
&& typeof item
.off
== 'function') {
56 item
.off(null, null, self
);
59 propertyValue
.length
= 0;
61 if (propertyValue
.detached
&& typeof propertyValue
.detached
== 'function') {
62 propertyValue
.detached();
63 } else if (propertyValue
.cancel
&& typeof propertyValue
.cancel
== 'function') {
64 propertyValue
.cancel();
65 } else if (propertyValue
.off
&& typeof propertyValue
.off
== 'function') {
66 propertyValue
.off(null, null, self
);
74 // call in to do additional cleanup
75 if (self
.doCleanup
&& typeof self
.doCleanup
== 'function') {
79 getSearchParams: function (url
) {
80 var a
= document
.createElement('a');
83 var items
= a
.search
.replace('?', '').split('&');
84 for (var i
= 0; i
< items
.length
; i
++) {
85 if (items
[i
].length
> 0) {
86 var key_value
= items
[i
].split('=');
87 params
[key_value
[0]] = key_value
[1];
93 inplaceUpdate : function(ary
, values
) {
94 var args
= [0, ary
.length
];
95 Array
.prototype.splice
.apply(ary
, args
.concat(values
));
99 // explore making this configurable somehow
100 // api_server = 'http://localhost:5050';
101 rw
.search_params
= rw
.getSearchParams(window
.location
.href
);
103 if (Element
.prototype == null) {
104 Element
.prototype.uniqueId
= 0;
107 Element
.prototype.generateUniqueId = function() {
108 Element
.prototype.uniqueId
++;
109 return 'uid' + Element
.prototype.uniqueId
;
112 Element
.prototype.empty
= Element
.prototype.empty
|| function() {
113 while(this.firstChild
) {
114 this.removeChild(this.firstChild
);
119 * Merge one object into another. No circular reference checking so if there
120 * is there might be infinite recursion.
122 rw
.merge = function(obj1
, obj2
) {
124 if (typeof(obj2
[prop
]) == 'object') {
126 this.merge(obj1
[prop
], obj2
[prop
]);
128 obj1
[prop
] = obj2
[prop
];
131 obj1
[prop
] = obj2
[prop
];
136 Element
.prototype.getElementByTagName = function(tagName
) {
137 for (var i
= this.children
.length
- 1; i
>= 0; i
--) {
138 if (this.children
[i
].localName
== tagName
) {
139 return this.children
[i
];
146 computedWidth : function(elem
, defaultValue
) {
147 var s
= window
.getComputedStyle(elem
);
149 if (w
&& w
!= 'auto') {
150 // I've never seen this case, but here anyway
160 computedHeight : function(elem
, defaultValue
) {
161 var s
= window
.getComputedStyle(elem
);
163 if (w
&& w
!= 'auto') {
164 // I've never seen this case, but here anyway
174 computedStyle : function(elem
, property
, defaultValue
) {
175 var s
= window
.getComputedStyle(elem
);
183 return Math
.abs(n
) % 2 == 1 ? 'odd' : '';
186 status : function(s
) {
187 return s
== 'OK' ? 'yes' : 'no';
190 capitalize: function(s
) {
191 return s
? s
.charAt(0).toUpperCase() + s
.slice(1) : '';
194 fmt: function(n
, fmtStr
) {
195 return numeral(n
).format(fmtStr
);
198 // assumes values are in megabytes!
199 bytes: function(n
, capacity
) {
200 if (n
=== undefined || isNaN(n
)) {
204 if (capacity
=== undefined) {
212 ['TB' , 1000000000000],
213 ['PB' , 1000000000000000]
215 for (var i
= 0; i
< suffixes
.length
; i
++) {
216 if (capacity
< suffixes
[i
][1]) {
217 return (numeral((n
* 1000) / suffixes
[i
][1]).format('0,0') + (units
? suffixes
[i
][0] : ''));
220 return n
+ (units
? 'B' : '');
223 // assumes values are already in megabits!
224 bits: function(n
, capacity
) {
225 if (n
=== undefined || isNaN(n
)) {
229 if (capacity
=== undefined) {
236 ['Tbps' , 1000000000],
237 ['Pbps' , 1000000000000]
239 for (var i
= 0; i
< suffixes
.length
; i
++) {
240 if (capacity
< suffixes
[i
][1]) {
241 return (numeral((n
* 1000) / suffixes
[i
][1]).format('0,0') + (units
? suffixes
[i
][0] : ''));
244 return n
+ (units
? 'Bps' : '');
247 ppsUtilization: function(pps
) {
248 return pps
? numeral(pps
/ 1000000).format('0.0') : '';
250 ppsUtilizationMax: function(item
) {
251 var rate
= item
.rate
/ 10000;
252 var max
= item
.max
* 0.0015;
255 bpsAsPps: function(speed
) {
256 return parseInt(speed
) * 0.0015;
259 upperCase: function(s
) {
260 return s
.toUpperCase()
263 mbpsAsPps: function(mbps
) {
264 var n
= parseInt(mbps
);
265 return isNaN(n
) ? 0 : rw
.ui
.fmt(rw
.ui
.bpsAsPps(n
* 1000000), '0a').toUpperCase();
269 return rw
.ui
.fmt(rw
.ui
.noNaN(n
), '0a');
273 return isNaN(n
) ? 0 : n
;
276 // Labels used in system
279 'trafsimclient': 'Traf Sim Client',
280 'trafsimserver': 'Traf Sim Server',
282 'ltegwsim': 'SAE Gateway',
283 'trafgen': 'Traf Gen Client',
284 'trafsink': 'Traf Gen Server',
285 'loadbal': 'Load Balancer',
286 'slbalancer': 'Scriptable Load Balancer'
292 editXml : function(xmlTemplate
, domEditor
) {
293 var str2dom
= new DOMParser();
294 var dom
= str2dom
.parseFromString(xmlTemplate
, 'text/xml');
298 var dom2str
= new XMLSerializer();
299 return dom2str
.serializeToString(dom
);
302 num : function(el
, tag
) {
303 return parseInt(this.str(el
, tag
), 10);
306 str : function(el
, tag
) {
307 var tags
= el
.getElementsByTagName(tag
);
308 return tags
.length
> 0 ? tags
[0].textContent
.trim() : '';
311 sum : function(total
, i
, key
, value
) {
312 if (_isNumber(value
)) {
313 total
[key
] = (i
=== 0 ? value
: (total
[key
] + value
));
317 sum2 : function(key
) {
318 return function(prev
, cur
, i
) {
319 var value
= cur
[key
];
320 if (_isNumber(value
)) {
321 if (typeof(prev
) === 'undefined') {
331 max : function(key
) {
332 return function(prev
, cur
, i
) {
333 var value
= cur
[key
];
334 if (_isNumber(value
)) {
335 if (typeof(prev
) === 'undefined') {
337 } else if (prev
< value
) {
345 avg2 : function(key
) {
346 var sum
= rw
.math
.sum2(key
);
347 return function(prev
, cur
, i
, ary
) {
348 var s
= sum(prev
, cur
, i
);
349 if (i
=== ary
.length
- 1) {
350 return s
/ ary
.length
;
356 avg : function(rows
, key
) {
357 var total
= XmlMath
.total(rows
, key
);
358 return total
/ rows
.length
;
361 total : function(rows
, key
) {
363 for (var i
= rows
.length
- 1; i
>= 0; i
--) {
364 var n
= parseInt(rows
[i
][key
]);
372 run : function(total
, rows
, operation
) {
374 var f = function(value
, key
) {
375 operation(total
, i
, key
, value
);
377 for (i
= 0; i
< rows
.length
; i
++) {
385 open: function (name
, onInit
, onOpen
) {
388 var open
= window
.indexedDB
.open(name
, 2);
390 open
.onerror = function (e
) {
391 console
.log('Could not open database', name
, e
.target
.error
.message
);
394 open
.onsuccess = function (e
) {
395 var db
= e
.target
.result
;
399 open
.onupgradeneeded = function (e
) {
400 var db
= e
.target
.result
;
406 rw
.db
.Offline = function(name
) {
408 this.datastore
= 'offline';
411 rw
.db
.Offline
.prototype = {
413 open : function(onOpen
) {
414 rw
.db
.open(this.name
, this.init
.bind(this), onOpen
);
417 getItem : function(url
, onData
) {
419 this.open(function(db
) {
420 var query
= db
.transaction(self
.datastore
)
421 .objectStore(self
.datastore
)
423 query
.onsuccess = function(e
) {
424 if (e
.target
.result
) {
425 onData(e
.target
.result
.data
);
427 console
.log('No data found for ' + url
+ '. You may need to rebuild your offline database');
433 init : function(db
) {
435 if (!db
.objectStoreNames
.contains(this.datastore
)) {
436 var create
= db
.createObjectStore(this.datastore
, {keyPath
: 'url'});
437 create
.onerror = function(e
) {
438 console
.log('Could not create object store ' + this.datastore
);
443 saveStore : function(store
) {
445 this.open(function(db
) {
446 for (var i
= 0; i
< store
.length
; i
++) {
447 var save
= db
.transaction(self
.datastore
, "readwrite")
448 .objectStore(self
.datastore
)
462 resetStats: function() {
463 rw
.api
.nRequests
= 0;
464 rw
.api
.tRequestsMs
= 0;
465 rw
.api
.tRequestMaxMs
= 0;
466 rw
.api
.nRequestsByUrl
= {};
469 handleAjaxError : function (req
, status
, err
) {
470 console
.log('failed', req
, status
, err
);
473 json: function(url
) {
474 return this.get(url
, 'application/json')
477 get: function(url
, accept
) {
478 var deferred
= jQuery
.Deferred();
479 if (rw
.api
.offline
) {
480 rw
.api
.offline
.getItem(url
, function (data
) {
481 deferred
.resolve(data
);
484 var startTime
= new Date().getTime();
485 jQuery
.ajax(rw
.api
.server
+ url
, {
488 error
: rw
.api
.handleAjaxError
,
489 headers
: rw
.api
.headers(accept
),
490 success: function (data
) {
491 rw
.api
.recordUrl(url
, startTime
);
492 deferred
.resolve(data
);
499 return deferred
.promise();
502 headers: function(accept
, contentType
) {
506 if (rw
.api
.statsId
!= null) {
507 h
['x-stats'] = rw
.api
.statsId
;
510 h
['Content-Type'] = contentType
;
515 recordUrl:function(url
, startTime
) {
516 var elapsed
= new Date().getTime() - startTime
;
517 rw
.api
.tRequestsMs
+= elapsed
;
518 rw
.api
.nRequests
+= 1;
519 rw
.api
.tRequestMaxMs
= Math
.max(rw
.api
.tRequestMaxMs
, elapsed
);
520 if (url
in rw
.api
.nRequestsByUrl
) {
521 var metric
= rw
.api
.nRequestsByUrl
[url
];
524 metric
.max
= Math
.max(metric
.max
, elapsed
);
526 rw
.api
.nRequestsByUrl
[url
] = {url
: url
, n
: 1, max
: elapsed
};
530 put: function(url
, data
, contentType
) {
531 return this.push('PUT', url
, data
, contentType
);
534 post: function(url
, data
, contentType
) {
535 return this.push('POST', url
, data
, contentType
);
538 rpc: function(url
, data
, error
) {
539 if(error
=== undefined){
540 error = function(a
,b
,c
){
543 return this.push('POST', url
, data
, 'application/vnd.yang.data+json', error
);
546 push: function(method
, url
, data
, contentType
, errorFn
) {
547 var deferred
= jQuery
.Deferred();
548 if (rw
.api
.offline
) {
549 // eating offline put request
550 if(contentType
== 'application/vnd.yang.data+json'){
552 rw
.api
.offline
.getItem(url
, function (data
) {
553 deferred
.resolve(data
);
556 deferred
.resolve({});
558 var startTime
= new Date().getTime();
559 jQuery
.ajax(rw
.api
.server
+ url
, {
561 error
: rw
.api
.handleAjaxError
,
563 headers
: rw
.api
.headers('application/json', contentType
),
564 data
: JSON
.stringify(data
),
565 success: function (response
) {
566 rw
.api
.recordUrl(url
, startTime
);
567 deferred
.resolve(response
);
572 return deferred
.promise();
575 setOffline: function(name
) {
577 rw
.api
.offline
= new rw
.db
.Offline(name
);
579 rw
.api
.offline
= false;
583 // When passing things to ConfD ('/api/...') then '/' needs to be
584 // %252F(browser) --> %2F(Rest) --> / ConfD
585 encodeUrlParam: function(value
) {
586 var once
= rw
.api
.singleEncodeUrlParam(value
);
587 return once
.replace(/%/g
, '%25');
590 // UrlParam cannot have '/' and encoding it using %2F gets double-encoded in flask
591 singleEncodeUrlParam: function(value
) {
592 return value
.replace(/\//g, '%2F');
596 rw
.api
.SocketSubscriber = function(url
) {
599 this.id
= ++rw
.api
.SocketSubscriber
.uniqueId
;
601 this.subscribed
= false;
602 this.offlineRateMs
= 2000;
605 rw
.api
.SocketSubscriber
.uniqueId
= 0;
607 rw
.api
.SocketSubscriber
.prototype = {
609 // does not support PUT/PORT with payloads requests yet
610 websubscribe : function(webUrl
, onload
, offline
) {
611 this.subscribeMeta(onload
, {
616 subscribeMeta : function(onload
, meta
, offline
) {
619 if (this.subscribed
) {
624 if (rw
.api
.offline
) {
625 this.subscribeOffline(onload
, m
, offline
);
627 this.subscribeOnline(onload
, m
);
631 subscribeOffline: function(onload
, meta
, offline
) {
633 rw
.api
.json(meta
.url
).done(function(data
) {
634 var _update = function() {
642 this.offlineTimer
= setInterval(_update
, self
.offlineRateMs
);
646 subscribeOnline: function(onload
, meta
) {
648 var _subscribe = function() {
649 meta
.widgetId
= self
.id
;
650 meta
.accept
= meta
.accept
|| 'application/json';
651 document
.websocket().emit(self
.url
, meta
);
652 self
.subscribed
= true;
655 var _register = function() {
656 document
.websocket().on(self
.url
+ '/' + self
.id
, function(dataString
) {
657 var data
= dataString
;
659 // auto convert to object to make backward compatible
660 if (meta
.accept
.match(/[+\/]json$/) && dataString
!= '') {
661 data
= JSON
.parse(dataString
);
667 document
.websocket().on('error',function(d
){
668 console
.log('socket error', d
)
670 document
.websocket().on('close',function(d
){
671 console
.log('socket close', d
)
673 document
.websocket().on('connect', _register
);
675 // it's possible this call is not nec. and will always be called
676 // as part of connect statement above
680 unsubscribe : function() {
681 if (rw
.api
.offline
) {
682 if (this.offlineTimer
) {
683 clearInterval(this.offlineTimer
);
684 this.offlineTimer
= null;
687 var unsubscribe
= { widgetId
: this.id
, enable
: false };
688 document
.websocket().emit(this.url
, unsubscribe
);
689 this.subscribed
= false;
694 rw
.api
.server
= rw
.search_params
['api_server'] || '';
696 document
.websocket = function() {
697 if ( ! this.socket
) {
698 //io.reconnection = true;
699 var wsServer
= rw
.api
.server
|| 'http://' + document
.domain
+ ':' + location
.port
;
700 var wsUrl
= wsServer
+ '/rwapp';
701 this.socket
= io
.connect(wsUrl
, {reconnection
:true});
706 rw
.api
.setOffline(rw
.search_params
['offline']);
709 ports: function(service
) {
710 return _flatten(jsonPath
.eval(service
, '$.connector[*].interface[*].port'));
712 fabricPorts: function(service
) {
713 return _flatten(jsonPath
.eval(service
, '$.vm[*].fabric.port'));
717 rw
.VcsVisitor = function(enter
, leave
) {
722 rw
.VcsVisitor
.prototype = {
724 visit: function(node
, parent
, i
, listType
) {
725 var hasChildren
= this.enter(parent
, node
, i
, listType
);
727 switch (rw
.vcs
.nodeType(node
)) {
729 this.visitChildren(node
, node
.collection
, 'collection');
732 this.visitChildren(node
, node
.collection
, 'collection');
733 this.visitChildren(node
, node
.vm
, 'vm');
736 this.visitChildren(node
, node
.vm
, 'vm');
739 this.visitChildren(node
, node
.process
, 'process');
742 this.visitChildren(node
, node
.tasklet
, 'tasklet');
747 this.leave(parent
, node
, obj
, i
, listType
);
751 visitChildren : function(parent
, children
, listType
) {
757 _each(children
, function(child
) {
758 self
.visit
.call(self
, child
, parent
, i
, listType
);
766 allVms : function() {
767 return _flatten([this.jpath('$.collection[*].vm'), this.jpath('$.collection[*].collection[*].vm')], true);
771 if (n
== undefined || n
=== null || n
=== this) {
772 return this.allVms();
774 switch (rw
.vcs
.nodeType(n
)) {
776 return this.jpath('$.collection[*].vm[*]', n
);
778 return this.jpath('$.vm[*]', n
);
786 nodeType: function(node
) {
787 if (node
.component_type
=== 'RWCOLLECTION') {
788 return node
.collection_info
['collection-type'];
790 return node
.component_type
;
793 allClusters : function() {
794 return this.jpath('$.collection[*].collection');
797 allColonies: function() {
798 return this.jpath('$.collection');
801 allPorts:function(n
) {
802 switch (rw
.vcs
.nodeType(n
)) {
804 return this.jpath('$.collection[*].collection[*].vm[*].port[*]', n
);
806 return this.jpath('$.collection[*].vm[*].port[*]', n
);
808 return this.jpath('$.vm[*].port[*]', n
);
810 return this.jpath('$.port[*]', n
);
816 allFabricPorts:function(n
) {
817 switch (rw
.vcs
.nodeType(n
)) {
819 return this.jpath('$.collection[*].collection[*].vm[*].fabric.port[*]', n
);
821 return this.jpath('$.collection[*].vm[*].fabric.port[*]', n
);
823 return this.jpath('$.vm[*].fabric.port[*]', n
);
825 return this.jpath('$.fabric.port[*]', n
);
831 getChildren: function(n
) {
832 switch (rw
.vcs
.nodeType(n
)) {
834 return 'vm' in n
? _union(n
.collection
, n
.vm
) : n
.collection
;
845 jpath : function(jpath
, n
) {
846 return _flatten(jsonPath
.eval(n
|| this, jpath
), true);
851 startedActual
: null, // true or false once server-side state is loaded
852 startedPerceived
: false,
854 rateActual
: null, // non-null once server-side state is loaded
855 packetSizePerceived
: 1024,
856 packetSizeActual
: null
860 startedActual
: null, // true or false once server-side state is loaded
861 startedPerceived
: false,
862 ratePerceived
: 50000,
863 rateActual
: null, // non-null once server-side state is loaded
867 rw
.aggregateControlPanel
= null;
870 // purple-2 and blue-2
871 txBps
: 'hsla(212, 57%, 50%, 1)',
872 txBpsTranslucent
: 'hsla(212, 57%, 50%, 0.7)',
873 rxBps
: 'hsla(212, 57%, 50%, 1)',
874 rxBpsTranslucent
: 'hsla(212, 57%, 50%, 0.7)',
875 txPps
: 'hsla(260, 35%, 50%, 1)',
876 txPpsTranslucent
: 'hsla(260, 35%, 50%, 0.7)',
877 rxPps
: 'hsla(260, 35%, 50%, 1)',
878 rxPpsTranslucent
: 'hsla(260, 35%, 50%, 0.7)',
879 memory
: 'hsla(27, 98%, 57%, 1)',
880 memoryTranslucent
: 'hsla(27, 98%, 57%, 0.7)',
881 cpu
: 'hsla(123, 45%, 50%, 1)',
882 cpuTranslucent
: 'hsla(123, 45%, 50%, 0.7)',
883 storage
: 'hsla(180, 78%, 25%, 1)',
884 storageTranslucent
: 'hsla(180, 78%, 25%, 0.7)'
887 rw
.RateTimer = function(onChange
, onStop
) {
889 this.onChange
= onChange
;
890 this.onStop
= onStop
;
891 this.testFrequency
= 500;
892 this.testWaveLength
= 5000;
897 rw
.RateTimer
.prototype = {
902 var strategy
= this.smoothRateStrategy
.bind(this);
903 this.testingStartTime
= new Date().getTime();
904 this.timer
= setInterval(strategy
, this.testFrequency
);
910 clearInterval(this.timer
);
916 smoothRateStrategy: function() {
917 var x
= (new Date().getTime() - this.testingStartTime
) / this.testWaveLength
;
918 this.rate
= Math
.round(100 * 0.5 * (1 - Math
.cos(x
)));
919 // in theory you could use wavelength and frequency to determine stop but this
920 // is guaranteed to stop at zero.
921 this.onChange(this.rate
);
923 if (this.n
>= 10 && this.rate
< 1) {