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'])
34 // Angular specific for now but can be modified in one location if need ever be
35 BaseController: function() {
38 // handles factories with detach/cancel methods and listeners with cancel method
40 this.$scope
.$on('$stateChangeStart', function() {
41 var properties
= Object
.getOwnPropertyNames(self
);
43 properties
.forEach(function(key
) {
45 var propertyValue
= self
[key
];
48 if (Array
.isArray(propertyValue
)) {
49 propertyValue
.forEach(function(item
) {
50 if (item
.off
&& typeof item
.off
== 'function') {
51 item
.off(null, null, self
);
54 propertyValue
.length
= 0;
56 if (propertyValue
.detached
&& typeof propertyValue
.detached
== 'function') {
57 propertyValue
.detached();
58 } else if (propertyValue
.cancel
&& typeof propertyValue
.cancel
== 'function') {
59 propertyValue
.cancel();
60 } else if (propertyValue
.off
&& typeof propertyValue
.off
== 'function') {
61 propertyValue
.off(null, null, self
);
69 // call in to do additional cleanup
70 if (self
.doCleanup
&& typeof self
.doCleanup
== 'function') {
74 getSearchParams: function (url
) {
75 var a
= document
.createElement('a');
78 var items
= a
.search
.replace('?', '').split('&');
79 for (var i
= 0; i
< items
.length
; i
++) {
80 if (items
[i
].length
> 0) {
81 var key_value
= items
[i
].split('=');
82 params
[key_value
[0]] = key_value
[1];
88 inplaceUpdate : function(ary
, values
) {
89 var args
= [0, ary
.length
];
90 Array
.prototype.splice
.apply(ary
, args
.concat(values
));
94 // explore making this configurable somehow
95 // api_server = 'http://localhost:5050';
96 rw
.search_params
= rw
.getSearchParams(window
.location
.href
);
98 if (Element
.prototype == null) {
99 Element
.prototype.uniqueId
= 0;
102 Element
.prototype.generateUniqueId = function() {
103 Element
.prototype.uniqueId
++;
104 return 'uid' + Element
.prototype.uniqueId
;
107 Element
.prototype.empty
= Element
.prototype.empty
|| function() {
108 while(this.firstChild
) {
109 this.removeChild(this.firstChild
);
114 * Merge one object into another. No circular reference checking so if there
115 * is there might be infinite recursion.
117 rw
.merge = function(obj1
, obj2
) {
119 if (typeof(obj2
[prop
]) == 'object') {
121 this.merge(obj1
[prop
], obj2
[prop
]);
123 obj1
[prop
] = obj2
[prop
];
126 obj1
[prop
] = obj2
[prop
];
131 Element
.prototype.getElementByTagName = function(tagName
) {
132 for (var i
= this.children
.length
- 1; i
>= 0; i
--) {
133 if (this.children
[i
].localName
== tagName
) {
134 return this.children
[i
];
141 computedWidth : function(elem
, defaultValue
) {
142 var s
= window
.getComputedStyle(elem
);
144 if (w
&& w
!= 'auto') {
145 // I've never seen this case, but here anyway
155 computedHeight : function(elem
, defaultValue
) {
156 var s
= window
.getComputedStyle(elem
);
158 if (w
&& w
!= 'auto') {
159 // I've never seen this case, but here anyway
169 computedStyle : function(elem
, property
, defaultValue
) {
170 var s
= window
.getComputedStyle(elem
);
178 return Math
.abs(n
) % 2 == 1 ? 'odd' : '';
181 status : function(s
) {
182 return s
== 'OK' ? 'yes' : 'no';
185 capitalize: function(s
) {
186 return s
? s
.charAt(0).toUpperCase() + s
.slice(1) : '';
189 fmt: function(n
, fmtStr
) {
190 return numeral(n
).format(fmtStr
);
193 // assumes values are in megabytes!
194 bytes: function(n
, capacity
) {
195 if (n
=== undefined || isNaN(n
)) {
199 if (capacity
=== undefined) {
207 ['TB' , 1000000000000],
208 ['PB' , 1000000000000000]
210 for (var i
= 0; i
< suffixes
.length
; i
++) {
211 if (capacity
< suffixes
[i
][1]) {
212 return (numeral((n
* 1000) / suffixes
[i
][1]).format('0,0') + (units
? suffixes
[i
][0] : ''));
215 return n
+ (units
? 'B' : '');
218 // assumes values are already in megabits!
219 bits: function(n
, capacity
) {
220 if (n
=== undefined || isNaN(n
)) {
224 if (capacity
=== undefined) {
231 ['Tbps' , 1000000000],
232 ['Pbps' , 1000000000000]
234 for (var i
= 0; i
< suffixes
.length
; i
++) {
235 if (capacity
< suffixes
[i
][1]) {
236 return (numeral((n
* 1000) / suffixes
[i
][1]).format('0,0') + (units
? suffixes
[i
][0] : ''));
239 return n
+ (units
? 'Bps' : '');
242 ppsUtilization: function(pps
) {
243 return pps
? numeral(pps
/ 1000000).format('0.0') : '';
245 ppsUtilizationMax: function(item
) {
246 var rate
= item
.rate
/ 10000;
247 var max
= item
.max
* 0.0015;
250 bpsAsPps: function(speed
) {
251 return parseInt(speed
) * 0.0015;
254 upperCase: function(s
) {
255 return s
.toUpperCase()
258 mbpsAsPps: function(mbps
) {
259 var n
= parseInt(mbps
);
260 return isNaN(n
) ? 0 : rw
.ui
.fmt(rw
.ui
.bpsAsPps(n
* 1000000), '0a').toUpperCase();
264 return rw
.ui
.fmt(rw
.ui
.noNaN(n
), '0a');
268 return isNaN(n
) ? 0 : n
;
271 // Labels used in system
274 'trafsimclient': 'Traf Sim Client',
275 'trafsimserver': 'Traf Sim Server',
277 'ltegwsim': 'SAE Gateway',
278 'trafgen': 'Traf Gen Client',
279 'trafsink': 'Traf Gen Server',
280 'loadbal': 'Load Balancer',
281 'slbalancer': 'Scriptable Load Balancer'
287 editXml : function(xmlTemplate
, domEditor
) {
288 var str2dom
= new DOMParser();
289 var dom
= str2dom
.parseFromString(xmlTemplate
, 'text/xml');
293 var dom2str
= new XMLSerializer();
294 return dom2str
.serializeToString(dom
);
297 num : function(el
, tag
) {
298 return parseInt(this.str(el
, tag
), 10);
301 str : function(el
, tag
) {
302 var tags
= el
.getElementsByTagName(tag
);
303 return tags
.length
> 0 ? tags
[0].textContent
.trim() : '';
306 sum : function(total
, i
, key
, value
) {
307 if (_
.isNumber(value
)) {
308 total
[key
] = (i
=== 0 ? value
: (total
[key
] + value
));
312 sum2 : function(key
) {
313 return function(prev
, cur
, i
) {
314 var value
= cur
[key
];
315 if (_
.isNumber(value
)) {
316 if (typeof(prev
) === 'undefined') {
326 max : function(key
) {
327 return function(prev
, cur
, i
) {
328 var value
= cur
[key
];
329 if (_
.isNumber(value
)) {
330 if (typeof(prev
) === 'undefined') {
332 } else if (prev
< value
) {
340 avg2 : function(key
) {
341 var sum
= rw
.math
.sum2(key
);
342 return function(prev
, cur
, i
, ary
) {
343 var s
= sum(prev
, cur
, i
);
344 if (i
=== ary
.length
- 1) {
345 return s
/ ary
.length
;
351 avg : function(rows
, key
) {
352 var total
= XmlMath
.total(rows
, key
);
353 return total
/ rows
.length
;
356 total : function(rows
, key
) {
358 for (var i
= rows
.length
- 1; i
>= 0; i
--) {
359 var n
= parseInt(rows
[i
][key
]);
367 run : function(total
, rows
, operation
) {
369 var f = function(value
, key
) {
370 operation(total
, i
, key
, value
);
372 for (i
= 0; i
< rows
.length
; i
++) {
380 open: function (name
, onInit
, onOpen
) {
383 var open
= window
.indexedDB
.open(name
, 2);
385 open
.onerror = function (e
) {
386 console
.log('Could not open database', name
, e
.target
.error
.message
);
389 open
.onsuccess = function (e
) {
390 var db
= e
.target
.result
;
394 open
.onupgradeneeded = function (e
) {
395 var db
= e
.target
.result
;
401 rw
.db
.Offline = function(name
) {
403 this.datastore
= 'offline';
406 rw
.db
.Offline
.prototype = {
408 open : function(onOpen
) {
409 rw
.db
.open(this.name
, this.init
.bind(this), onOpen
);
412 getItem : function(url
, onData
) {
414 this.open(function(db
) {
415 var query
= db
.transaction(self
.datastore
)
416 .objectStore(self
.datastore
)
418 query
.onsuccess = function(e
) {
419 if (e
.target
.result
) {
420 onData(e
.target
.result
.data
);
422 console
.log('No data found for ' + url
+ '. You may need to rebuild your offline database');
428 init : function(db
) {
430 if (!db
.objectStoreNames
.contains(this.datastore
)) {
431 var create
= db
.createObjectStore(this.datastore
, {keyPath
: 'url'});
432 create
.onerror = function(e
) {
433 console
.log('Could not create object store ' + this.datastore
);
438 saveStore : function(store
) {
440 this.open(function(db
) {
441 for (var i
= 0; i
< store
.length
; i
++) {
442 var save
= db
.transaction(self
.datastore
, "readwrite")
443 .objectStore(self
.datastore
)
457 resetStats: function() {
458 rw
.api
.nRequests
= 0;
459 rw
.api
.tRequestsMs
= 0;
460 rw
.api
.tRequestMaxMs
= 0;
461 rw
.api
.nRequestsByUrl
= {};
464 handleAjaxError : function (req
, status
, err
) {
465 console
.log('failed', req
, status
, err
);
468 json: function(url
) {
469 return this.get(url
, 'application/json')
472 get: function(url
, accept
) {
473 var deferred
= jQuery
.Deferred();
474 if (rw
.api
.offline
) {
475 rw
.api
.offline
.getItem(url
, function (data
) {
476 deferred
.resolve(data
);
479 var startTime
= new Date().getTime();
480 jQuery
.ajax(rw
.api
.server
+ url
, {
483 error
: rw
.api
.handleAjaxError
,
484 headers
: rw
.api
.headers(accept
),
485 success: function (data
) {
486 rw
.api
.recordUrl(url
, startTime
);
487 deferred
.resolve(data
);
494 return deferred
.promise();
497 headers: function(accept
, contentType
) {
501 if (rw
.api
.statsId
!= null) {
502 h
['x-stats'] = rw
.api
.statsId
;
505 h
['Content-Type'] = contentType
;
510 recordUrl:function(url
, startTime
) {
511 var elapsed
= new Date().getTime() - startTime
;
512 rw
.api
.tRequestsMs
+= elapsed
;
513 rw
.api
.nRequests
+= 1;
514 rw
.api
.tRequestMaxMs
= Math
.max(rw
.api
.tRequestMaxMs
, elapsed
);
515 if (url
in rw
.api
.nRequestsByUrl
) {
516 var metric
= rw
.api
.nRequestsByUrl
[url
];
519 metric
.max
= Math
.max(metric
.max
, elapsed
);
521 rw
.api
.nRequestsByUrl
[url
] = {url
: url
, n
: 1, max
: elapsed
};
525 put: function(url
, data
, contentType
) {
526 return this.push('PUT', url
, data
, contentType
);
529 post: function(url
, data
, contentType
) {
530 return this.push('POST', url
, data
, contentType
);
533 rpc: function(url
, data
, error
) {
534 if(error
=== undefined){
535 error = function(a
,b
,c
){
538 return this.push('POST', url
, data
, 'application/vnd.yang.data+json', error
);
541 push: function(method
, url
, data
, contentType
, errorFn
) {
542 var deferred
= jQuery
.Deferred();
543 if (rw
.api
.offline
) {
544 // eating offline put request
545 if(contentType
== 'application/vnd.yang.data+json'){
547 rw
.api
.offline
.getItem(url
, function (data
) {
548 deferred
.resolve(data
);
551 deferred
.resolve({});
553 var startTime
= new Date().getTime();
554 jQuery
.ajax(rw
.api
.server
+ url
, {
556 error
: rw
.api
.handleAjaxError
,
558 headers
: rw
.api
.headers('application/json', contentType
),
559 data
: JSON
.stringify(data
),
560 success: function (response
) {
561 rw
.api
.recordUrl(url
, startTime
);
562 deferred
.resolve(response
);
567 return deferred
.promise();
570 setOffline: function(name
) {
572 rw
.api
.offline
= new rw
.db
.Offline(name
);
574 rw
.api
.offline
= false;
578 // When passing things to ConfD ('/api/...') then '/' needs to be
579 // %252F(browser) --> %2F(Rest) --> / ConfD
580 encodeUrlParam: function(value
) {
581 var once
= rw
.api
.singleEncodeUrlParam(value
);
582 return once
.replace(/%/g
, '%25');
585 // UrlParam cannot have '/' and encoding it using %2F gets double-encoded in flask
586 singleEncodeUrlParam: function(value
) {
587 return value
.replace(/\//g, '%2F');
591 rw
.api
.SocketSubscriber = function(url
) {
594 this.id
= ++rw
.api
.SocketSubscriber
.uniqueId
;
596 this.subscribed
= false;
597 this.offlineRateMs
= 2000;
600 rw
.api
.SocketSubscriber
.uniqueId
= 0;
602 rw
.api
.SocketSubscriber
.prototype = {
604 // does not support PUT/PORT with payloads requests yet
605 websubscribe : function(webUrl
, onload
, offline
) {
606 this.subscribeMeta(onload
, {
611 subscribeMeta : function(onload
, meta
, offline
) {
614 if (this.subscribed
) {
619 if (rw
.api
.offline
) {
620 this.subscribeOffline(onload
, m
, offline
);
622 this.subscribeOnline(onload
, m
);
626 subscribeOffline: function(onload
, meta
, offline
) {
628 rw
.api
.json(meta
.url
).done(function(data
) {
629 var _update = function() {
637 this.offlineTimer
= setInterval(_update
, self
.offlineRateMs
);
641 subscribeOnline: function(onload
, meta
) {
643 var _subscribe = function() {
644 meta
.widgetId
= self
.id
;
645 meta
.accept
= meta
.accept
|| 'application/json';
646 document
.websocket().emit(self
.url
, meta
);
647 self
.subscribed
= true;
650 var _register = function() {
651 document
.websocket().on(self
.url
+ '/' + self
.id
, function(dataString
) {
652 var data
= dataString
;
654 // auto convert to object to make backward compatible
655 if (meta
.accept
.match(/[+\/]json$/) && dataString
!= '') {
656 data
= JSON
.parse(dataString
);
662 document
.websocket().on('error',function(d
){
663 console
.log('socket error', d
)
665 document
.websocket().on('close',function(d
){
666 console
.log('socket close', d
)
668 document
.websocket().on('connect', _register
);
670 // it's possible this call is not nec. and will always be called
671 // as part of connect statement above
675 unsubscribe : function() {
676 if (rw
.api
.offline
) {
677 if (this.offlineTimer
) {
678 clearInterval(this.offlineTimer
);
679 this.offlineTimer
= null;
682 var unsubscribe
= { widgetId
: this.id
, enable
: false };
683 document
.websocket().emit(this.url
, unsubscribe
);
684 this.subscribed
= false;
689 rw
.api
.server
= rw
.search_params
['api_server'] || '';
691 document
.websocket = function() {
692 if ( ! this.socket
) {
693 //io.reconnection = true;
694 var wsServer
= rw
.api
.server
|| 'http://' + document
.domain
+ ':' + location
.port
;
695 var wsUrl
= wsServer
+ '/rwapp';
696 this.socket
= io
.connect(wsUrl
, {reconnection
:true});
701 rw
.api
.setOffline(rw
.search_params
['offline']);
704 ports: function(service
) {
705 return _
.flatten(jsonPath
.eval(service
, '$.connector[*].interface[*].port'));
707 fabricPorts: function(service
) {
708 return _
.flatten(jsonPath
.eval(service
, '$.vm[*].fabric.port'));
712 rw
.VcsVisitor = function(enter
, leave
) {
717 rw
.VcsVisitor
.prototype = {
719 visit: function(node
, parent
, i
, listType
) {
720 var hasChildren
= this.enter(parent
, node
, i
, listType
);
722 switch (rw
.vcs
.nodeType(node
)) {
724 this.visitChildren(node
, node
.collection
, 'collection');
727 this.visitChildren(node
, node
.collection
, 'collection');
728 this.visitChildren(node
, node
.vm
, 'vm');
731 this.visitChildren(node
, node
.vm
, 'vm');
734 this.visitChildren(node
, node
.process
, 'process');
737 this.visitChildren(node
, node
.tasklet
, 'tasklet');
742 this.leave(parent
, node
, obj
, i
, listType
);
746 visitChildren : function(parent
, children
, listType
) {
752 _
.each(children
, function(child
) {
753 self
.visit
.call(self
, child
, parent
, i
, listType
);
761 allVms : function() {
762 return _
.flatten([this.jpath('$.collection[*].vm'), this.jpath('$.collection[*].collection[*].vm')], true);
766 if (n
== undefined || n
=== null || n
=== this) {
767 return this.allVms();
769 switch (rw
.vcs
.nodeType(n
)) {
771 return this.jpath('$.collection[*].vm[*]', n
);
773 return this.jpath('$.vm[*]', n
);
781 nodeType: function(node
) {
782 if (node
.component_type
=== 'RWCOLLECTION') {
783 return node
.collection_info
['collection-type'];
785 return node
.component_type
;
788 allClusters : function() {
789 return this.jpath('$.collection[*].collection');
792 allColonies: function() {
793 return this.jpath('$.collection');
796 allPorts:function(n
) {
797 switch (rw
.vcs
.nodeType(n
)) {
799 return this.jpath('$.collection[*].collection[*].vm[*].port[*]', n
);
801 return this.jpath('$.collection[*].vm[*].port[*]', n
);
803 return this.jpath('$.vm[*].port[*]', n
);
805 return this.jpath('$.port[*]', n
);
811 allFabricPorts:function(n
) {
812 switch (rw
.vcs
.nodeType(n
)) {
814 return this.jpath('$.collection[*].collection[*].vm[*].fabric.port[*]', n
);
816 return this.jpath('$.collection[*].vm[*].fabric.port[*]', n
);
818 return this.jpath('$.vm[*].fabric.port[*]', n
);
820 return this.jpath('$.fabric.port[*]', n
);
826 getChildren: function(n
) {
827 switch (rw
.vcs
.nodeType(n
)) {
829 return 'vm' in n
? _
.union(n
.collection
, n
.vm
) : n
.collection
;
840 jpath : function(jpath
, n
) {
841 return _
.flatten(jsonPath
.eval(n
|| this, jpath
), true);
846 startedActual
: null, // true or false once server-side state is loaded
847 startedPerceived
: false,
849 rateActual
: null, // non-null once server-side state is loaded
850 packetSizePerceived
: 1024,
851 packetSizeActual
: null
855 startedActual
: null, // true or false once server-side state is loaded
856 startedPerceived
: false,
857 ratePerceived
: 50000,
858 rateActual
: null, // non-null once server-side state is loaded
862 rw
.aggregateControlPanel
= null;
865 // purple-2 and blue-2
866 txBps
: 'hsla(212, 57%, 50%, 1)',
867 txBpsTranslucent
: 'hsla(212, 57%, 50%, 0.7)',
868 rxBps
: 'hsla(212, 57%, 50%, 1)',
869 rxBpsTranslucent
: 'hsla(212, 57%, 50%, 0.7)',
870 txPps
: 'hsla(260, 35%, 50%, 1)',
871 txPpsTranslucent
: 'hsla(260, 35%, 50%, 0.7)',
872 rxPps
: 'hsla(260, 35%, 50%, 1)',
873 rxPpsTranslucent
: 'hsla(260, 35%, 50%, 0.7)',
874 memory
: 'hsla(27, 98%, 57%, 1)',
875 memoryTranslucent
: 'hsla(27, 98%, 57%, 0.7)',
876 cpu
: 'hsla(123, 45%, 50%, 1)',
877 cpuTranslucent
: 'hsla(123, 45%, 50%, 0.7)',
878 storage
: 'hsla(180, 78%, 25%, 1)',
879 storageTranslucent
: 'hsla(180, 78%, 25%, 0.7)'
882 rw
.RateTimer = function(onChange
, onStop
) {
884 this.onChange
= onChange
;
885 this.onStop
= onStop
;
886 this.testFrequency
= 500;
887 this.testWaveLength
= 5000;
892 rw
.RateTimer
.prototype = {
897 var strategy
= this.smoothRateStrategy
.bind(this);
898 this.testingStartTime
= new Date().getTime();
899 this.timer
= setInterval(strategy
, this.testFrequency
);
905 clearInterval(this.timer
);
911 smoothRateStrategy: function() {
912 var x
= (new Date().getTime() - this.testingStartTime
) / this.testWaveLength
;
913 this.rate
= Math
.round(100 * 0.5 * (1 - Math
.cos(x
)));
914 // in theory you could use wavelength and frequency to determine stop but this
915 // is guaranteed to stop at zero.
916 this.onChange(this.rate
);
918 if (this.n
>= 10 && this.rate
< 1) {