RIFT-15726 - optimize download size -> lodash usage in UI
[osm/UI.git] / skyquake / framework / utils / rw.js
1
2 /*
3 *
4 * Copyright 2016 RIFT.IO Inc
5 *
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
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
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.
17 *
18 */
19
20 // rw.js will no longer be necessary when Angular dependency no longer exists
21 /**
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.
25 *
26 * Example:
27 * x = ['a', 'b']
28 * setValues(x, ['c', 'd'])
29 * x
30 * ['c', 'd']
31 */
32
33 import _isNumber from 'lodash/isNumber';
34 import _each from 'lodash/each';
35 import _flatten from 'lodash/flatten';
36 import _union from 'lodash/union';
37
38 var rw = rw || {
39 // Angular specific for now but can be modified in one location if need ever be
40 BaseController: function() {
41 var self = this;
42
43 // handles factories with detach/cancel methods and listeners with cancel method
44 if (this.$scope) {
45 this.$scope.$on('$stateChangeStart', function() {
46 var properties = Object.getOwnPropertyNames(self);
47
48 properties.forEach(function(key) {
49
50 var propertyValue = self[key];
51
52 if (propertyValue) {
53 if (Array.isArray(propertyValue)) {
54 propertyValue.forEach(function(item) {
55 if (item.off && typeof item.off == 'function') {
56 item.off(null, null, self);
57 }
58 });
59 propertyValue.length = 0;
60 } else {
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);
67 }
68 }
69 }
70 });
71 });
72 };
73
74 // call in to do additional cleanup
75 if (self.doCleanup && typeof self.doCleanup == 'function') {
76 self.doCleanup();
77 };
78 },
79 getSearchParams: function (url) {
80 var a = document.createElement('a');
81 a.href = url;
82 var params = {};
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];
88 }
89 }
90 return params;
91 },
92
93 inplaceUpdate : function(ary, values) {
94 var args = [0, ary.length];
95 Array.prototype.splice.apply(ary, args.concat(values));
96 }
97 };
98
99 // explore making this configurable somehow
100 // api_server = 'http://localhost:5050';
101 rw.search_params = rw.getSearchParams(window.location.href);
102 // MONKEY PATCHING
103 if (Element.prototype == null) {
104 Element.prototype.uniqueId = 0;
105 }
106
107 Element.prototype.generateUniqueId = function() {
108 Element.prototype.uniqueId++;
109 return 'uid' + Element.prototype.uniqueId;
110 };
111
112 Element.prototype.empty = Element.prototype.empty || function() {
113 while(this.firstChild) {
114 this.removeChild(this.firstChild);
115 }
116 };
117
118 /**
119 * Merge one object into another. No circular reference checking so if there
120 * is there might be infinite recursion.
121 */
122 rw.merge = function(obj1, obj2) {
123 for (prop in obj2) {
124 if (typeof(obj2[prop]) == 'object') {
125 if (prop in obj1) {
126 this.merge(obj1[prop], obj2[prop]);
127 } else {
128 obj1[prop] = obj2[prop];
129 }
130 } else {
131 obj1[prop] = obj2[prop];
132 }
133 }
134 }
135
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];
140 }
141 }
142 };
143
144 rw.ui = {
145
146 computedWidth : function(elem, defaultValue) {
147 var s = window.getComputedStyle(elem);
148 var w = s['width'];
149 if (w && w != 'auto') {
150 // I've never seen this case, but here anyway
151 return w;
152 }
153 w = s['min-width'];
154 if (w) {
155 return w;
156 }
157 return defaultValue;
158 },
159
160 computedHeight : function(elem, defaultValue) {
161 var s = window.getComputedStyle(elem);
162 var w = s['height'];
163 if (w && w != 'auto') {
164 // I've never seen this case, but here anyway
165 return w;
166 }
167 w = s['min-height'];
168 if (w) {
169 return w;
170 }
171 return defaultValue;
172 },
173
174 computedStyle : function(elem, property, defaultValue) {
175 var s = window.getComputedStyle(elem);
176 if (s[property]) {
177 return s[property];
178 }
179 return defaultValue;
180 },
181
182 odd : function(n) {
183 return Math.abs(n) % 2 == 1 ? 'odd' : '';
184 },
185
186 status : function(s) {
187 return s == 'OK' ? 'yes' : 'no';
188 },
189
190 capitalize: function(s) {
191 return s ? s.charAt(0).toUpperCase() + s.slice(1) : '';
192 },
193
194 fmt: function(n, fmtStr) {
195 return numeral(n).format(fmtStr);
196 },
197
198 // assumes values are in megabytes!
199 bytes: function(n, capacity) {
200 if (n === undefined || isNaN(n)) {
201 return '';
202 }
203 var units = false;
204 if (capacity === undefined) {
205 capacity = n;
206 units = true;
207 }
208 var suffixes = [
209 ['KB' , 1000],
210 ['MB' , 1000000],
211 ['GB' , 1000000000],
212 ['TB' , 1000000000000],
213 ['PB' , 1000000000000000]
214 ];
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] : ''));
218 }
219 }
220 return n + (units ? 'B' : '');
221 },
222
223 // assumes values are already in megabits!
224 bits: function(n, capacity) {
225 if (n === undefined || isNaN(n)) {
226 return '';
227 }
228 var units = false;
229 if (capacity === undefined) {
230 capacity = n;
231 units = true;
232 }
233 var suffixes = [
234 ['Mbps' , 1000],
235 ['Gbps' , 1000000],
236 ['Tbps' , 1000000000],
237 ['Pbps' , 1000000000000]
238 ];
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] : ''));
242 }
243 }
244 return n + (units ? 'Bps' : '');
245 },
246
247 ppsUtilization: function(pps) {
248 return pps ? numeral(pps / 1000000).format('0.0') : '';
249 },
250 ppsUtilizationMax: function(item) {
251 var rate = item.rate / 10000;
252 var max = item.max * 0.0015;
253 return rate/max;
254 },
255 bpsAsPps: function(speed) {
256 return parseInt(speed) * 0.0015;
257 },
258
259 upperCase: function(s) {
260 return s.toUpperCase()
261 },
262
263 mbpsAsPps: function(mbps) {
264 var n = parseInt(mbps);
265 return isNaN(n) ? 0 : rw.ui.fmt(rw.ui.bpsAsPps(n * 1000000), '0a').toUpperCase();
266 },
267
268 k: function(n) {
269 return rw.ui.fmt(rw.ui.noNaN(n), '0a');
270 },
271
272 noNaN: function(n) {
273 return isNaN(n) ? 0 : n;
274 },
275
276 // Labels used in system
277 l10n : {
278 vnf: {
279 'trafsimclient': 'Traf Sim Client',
280 'trafsimserver': 'Traf Sim Server',
281 'ltemmesim': 'MME',
282 'ltegwsim': 'SAE Gateway',
283 'trafgen': 'Traf Gen Client',
284 'trafsink': 'Traf Gen Server',
285 'loadbal': 'Load Balancer',
286 'slbalancer': 'Scriptable Load Balancer'
287 }
288 }
289 };
290
291 rw.math = {
292 editXml : function(xmlTemplate, domEditor) {
293 var str2dom = new DOMParser();
294 var dom = str2dom.parseFromString(xmlTemplate, 'text/xml');
295 if (domEditor) {
296 domEditor(dom);
297 }
298 var dom2str = new XMLSerializer();
299 return dom2str.serializeToString(dom);
300 },
301
302 num : function(el, tag) {
303 return parseInt(this.str(el, tag), 10);
304 },
305
306 str : function(el, tag) {
307 var tags = el.getElementsByTagName(tag);
308 return tags.length > 0 ? tags[0].textContent.trim() : '';
309 },
310
311 sum : function(total, i, key, value) {
312 if (_isNumber(value)) {
313 total[key] = (i === 0 ? value : (total[key] + value));
314 }
315 },
316
317 sum2 : function(key) {
318 return function(prev, cur, i) {
319 var value = cur[key];
320 if (_isNumber(value)) {
321 if (typeof(prev) === 'undefined') {
322 return value;
323 } else {
324 return prev + value;
325 }
326 }
327 return prev;
328 };
329 },
330
331 max : function(key) {
332 return function(prev, cur, i) {
333 var value = cur[key];
334 if (_isNumber(value)) {
335 if (typeof(prev) === 'undefined') {
336 return value;
337 } else if (prev < value) {
338 return value;
339 }
340 }
341 return prev;
342 };
343 },
344
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;
351 }
352 return s;
353 };
354 },
355
356 avg : function(rows, key) {
357 var total = XmlMath.total(rows, key);
358 return total / rows.length;
359 },
360
361 total : function(rows, key) {
362 var total = 0;
363 for (var i = rows.length - 1; i >= 0; i--) {
364 var n = parseInt(rows[i][key]);
365 if (!isNaN(n)) {
366 total += n;
367 }
368 }
369 return total;
370 },
371
372 run : function(total, rows, operation) {
373 var i;
374 var f = function(value, key) {
375 operation(total, i, key, value);
376 };
377 for (i = 0; i < rows.length; i++) {
378 _each(rows[i], f);
379 }
380 }
381 };
382
383
384 rw.db = {
385 open: function (name, onInit, onOpen) {
386 var self = this;
387
388 var open = window.indexedDB.open(name, 2);
389
390 open.onerror = function (e) {
391 console.log('Could not open database', name, e.target.error.message);
392 };
393
394 open.onsuccess = function (e) {
395 var db = e.target.result;
396 onOpen(db);
397 };
398
399 open.onupgradeneeded = function (e) {
400 var db = e.target.result;
401 onInit(db);
402 };
403 }
404 };
405
406 rw.db.Offline = function(name) {
407 this.name = name;
408 this.datastore = 'offline';
409 };
410
411 rw.db.Offline.prototype = {
412
413 open : function(onOpen) {
414 rw.db.open(this.name, this.init.bind(this), onOpen);
415 },
416
417 getItem : function(url, onData) {
418 var self = this;
419 this.open(function(db) {
420 var query = db.transaction(self.datastore)
421 .objectStore(self.datastore)
422 .get(url);
423 query.onsuccess = function(e) {
424 if (e.target.result) {
425 onData(e.target.result.data);
426 } else {
427 console.log('No data found for ' + url + '. You may need to rebuild your offline database');
428 }
429 }
430 });
431 },
432
433 init : function(db) {
434 var self = this;
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);
439 }
440 }
441 },
442
443 saveStore : function(store) {
444 var self = this;
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)
449 .put(store[i]);
450 }
451 });
452 }
453 };
454
455 rw.api = {
456 nRequests : 0,
457 tRequestsMs: 0,
458 tRequestMaxMs: 0,
459 nRequestsByUrl: {},
460 statsId: null,
461
462 resetStats: function() {
463 rw.api.nRequests = 0;
464 rw.api.tRequestsMs = 0;
465 rw.api.tRequestMaxMs = 0;
466 rw.api.nRequestsByUrl = {};
467 },
468
469 handleAjaxError : function (req, status, err) {
470 console.log('failed', req, status, err);
471 },
472
473 json: function(url) {
474 return this.get(url, 'application/json')
475 },
476
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);
482 });
483 } else {
484 var startTime = new Date().getTime();
485 jQuery.ajax(rw.api.server + url, {
486 type: 'GET',
487 dataType: 'json',
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);
493 },
494 error: function(e) {
495 deferred.reject(e);
496 }
497 });
498 }
499 return deferred.promise();
500 },
501
502 headers: function(accept, contentType) {
503 var h = {
504 Accept: accept
505 };
506 if (rw.api.statsId != null) {
507 h['x-stats'] = rw.api.statsId;
508 }
509 if (contentType) {
510 h['Content-Type'] = contentType;
511 }
512 return h;
513 },
514
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];
522 metric.url = url;
523 metric.n += 1;
524 metric.max = Math.max(metric.max, elapsed);
525 } else {
526 rw.api.nRequestsByUrl[url] = {url: url, n: 1, max: elapsed};
527 }
528 },
529
530 put: function(url, data, contentType) {
531 return this.push('PUT', url, data, contentType);
532 },
533
534 post: function(url, data, contentType) {
535 return this.push('POST', url, data, contentType);
536 },
537
538 rpc: function(url, data, error) {
539 if(error === undefined){
540 error = function(a,b,c){
541 }
542 }
543 return this.push('POST', url, data, 'application/vnd.yang.data+json', error);
544 },
545
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'){
551 var payload = data;
552 rw.api.offline.getItem(url, function (data) {
553 deferred.resolve(data);
554 });
555 }
556 deferred.resolve({});
557 } else {
558 var startTime = new Date().getTime();
559 jQuery.ajax(rw.api.server + url, {
560 type: method,
561 error: rw.api.handleAjaxError,
562 dataType: 'json',
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);
568 },
569 error: errorFn
570 });
571 }
572 return deferred.promise();
573 },
574
575 setOffline: function(name) {
576 if (name) {
577 rw.api.offline = new rw.db.Offline(name);
578 } else {
579 rw.api.offline = false;
580 }
581 },
582
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');
588 },
589
590 // UrlParam cannot have '/' and encoding it using %2F gets double-encoded in flask
591 singleEncodeUrlParam: function(value) {
592 return value.replace(/\//g, '%2F');
593 }
594 };
595
596 rw.api.SocketSubscriber = function(url) {
597 this.url = url;
598
599 this.id = ++rw.api.SocketSubscriber.uniqueId;
600
601 this.subscribed = false;
602 this.offlineRateMs = 2000;
603 },
604
605 rw.api.SocketSubscriber.uniqueId = 0;
606
607 rw.api.SocketSubscriber.prototype = {
608
609 // does not support PUT/PORT with payloads requests yet
610 websubscribe : function(webUrl, onload, offline) {
611 this.subscribeMeta(onload, {
612 url: webUrl
613 }, offline);
614 },
615
616 subscribeMeta : function(onload, meta, offline) {
617 var self = this;
618
619 if (this.subscribed) {
620 this.unsubscribe();
621 }
622
623 var m = meta || {};
624 if (rw.api.offline) {
625 this.subscribeOffline(onload, m, offline);
626 } else {
627 this.subscribeOnline(onload, m);
628 }
629 },
630
631 subscribeOffline: function(onload, meta, offline) {
632 var self = this;
633 rw.api.json(meta.url).done(function(data) {
634 var _update = function() {
635 if (offline) {
636 offline(data);
637 } else {
638 onload(data);
639 }
640 };
641
642 this.offlineTimer = setInterval(_update, self.offlineRateMs);
643 });
644 },
645
646 subscribeOnline: function(onload, meta) {
647 var self = this;
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;
653 };
654
655 var _register = function() {
656 document.websocket().on(self.url + '/' + self.id, function(dataString) {
657 var data = dataString;
658
659 // auto convert to object to make backward compatible
660 if (meta.accept.match(/[+\/]json$/) && dataString != '') {
661 data = JSON.parse(dataString);
662 }
663 onload(data);
664 });
665 _subscribe();
666 };
667 document.websocket().on('error',function(d){
668 console.log('socket error', d)
669 });
670 document.websocket().on('close',function(d){
671 console.log('socket close', d)
672 })
673 document.websocket().on('connect', _register);
674
675 // it's possible this call is not nec. and will always be called
676 // as part of connect statement above
677 _register();
678 },
679
680 unsubscribe : function() {
681 if (rw.api.offline) {
682 if (this.offlineTimer) {
683 clearInterval(this.offlineTimer);
684 this.offlineTimer = null;
685 }
686 } else {
687 var unsubscribe = { widgetId: this.id, enable: false };
688 document.websocket().emit(this.url, unsubscribe);
689 this.subscribed = false;
690 }
691 }
692 };
693
694 rw.api.server = rw.search_params['api_server'] || '';
695
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});
702 }
703 return this.socket;
704 };
705
706 rw.api.setOffline(rw.search_params['offline']);
707
708 rw.vnf = {
709 ports: function(service) {
710 return _flatten(jsonPath.eval(service, '$.connector[*].interface[*].port'));
711 },
712 fabricPorts: function(service) {
713 return _flatten(jsonPath.eval(service, '$.vm[*].fabric.port'));
714 }
715 };
716
717 rw.VcsVisitor = function(enter, leave) {
718 this.enter = enter;
719 this.leave = leave;
720 }
721
722 rw.VcsVisitor.prototype = {
723
724 visit: function(node, parent, i, listType) {
725 var hasChildren = this.enter(parent, node, i, listType);
726 if (hasChildren) {
727 switch (rw.vcs.nodeType(node)) {
728 case 'rwsector':
729 this.visitChildren(node, node.collection, 'collection');
730 break;
731 case 'rwcolony':
732 this.visitChildren(node, node.collection, 'collection');
733 this.visitChildren(node, node.vm, 'vm');
734 break;
735 case 'rwcluster':
736 this.visitChildren(node, node.vm, 'vm');
737 break;
738 case 'RWVM':
739 this.visitChildren(node, node.process, 'process');
740 break;
741 case 'RWPROC':
742 this.visitChildren(node, node.tasklet, 'tasklet');
743 break;
744 }
745 }
746 if (this.leave) {
747 this.leave(parent, node, obj, i, listType);
748 }
749 },
750
751 visitChildren : function(parent, children, listType) {
752 if (!children) {
753 return;
754 }
755 var i = 0;
756 var self = this;
757 _each(children, function(child) {
758 self.visit.call(self, child, parent, i, listType);
759 i += 1;
760 });
761 }
762 };
763
764 rw.vcs = {
765
766 allVms : function() {
767 return _flatten([this.jpath('$.collection[*].vm'), this.jpath('$.collection[*].collection[*].vm')], true);
768 },
769
770 vms: function(n) {
771 if (n == undefined || n === null || n === this) {
772 return this.allVms();
773 }
774 switch (rw.vcs.nodeType(n)) {
775 case 'rwcolony':
776 return this.jpath('$.collection[*].vm[*]', n);
777 case 'rwcluster':
778 return this.jpath('$.vm[*]', n);
779 case 'RWVM':
780 return [n];
781 default:
782 return null;
783 }
784 },
785
786 nodeType: function(node) {
787 if (node.component_type === 'RWCOLLECTION') {
788 return node.collection_info['collection-type'];
789 }
790 return node.component_type;
791 },
792
793 allClusters : function() {
794 return this.jpath('$.collection[*].collection');
795 },
796
797 allColonies: function() {
798 return this.jpath('$.collection');
799 },
800
801 allPorts:function(n) {
802 switch (rw.vcs.nodeType(n)) {
803 case 'rwsector':
804 return this.jpath('$.collection[*].collection[*].vm[*].port[*]', n);
805 case 'rwcolony':
806 return this.jpath('$.collection[*].vm[*].port[*]', n);
807 case 'rwcluster':
808 return this.jpath('$.vm[*].port[*]', n);
809 case 'RWVM':
810 return this.jpath('$.port[*]', n);
811 default:
812 return null;
813 }
814 },
815
816 allFabricPorts:function(n) {
817 switch (rw.vcs.nodeType(n)) {
818 case 'rwsector':
819 return this.jpath('$.collection[*].collection[*].vm[*].fabric.port[*]', n);
820 case 'rwcolony':
821 return this.jpath('$.collection[*].vm[*].fabric.port[*]', n);
822 case 'rwcluster':
823 return this.jpath('$.vm[*].fabric.port[*]', n);
824 case 'RWVM':
825 return this.jpath('$.fabric.port[*]', n);
826 default:
827 return null;
828 }
829 },
830
831 getChildren: function(n) {
832 switch (rw.vcs.nodeType(n)) {
833 case 'rwcolony':
834 return 'vm' in n ? _union(n.collection, n.vm) : n.collection;
835 case 'rwcluster':
836 return n.vm;
837 case 'RWVM':
838 return n.process;
839 case 'RWPROC':
840 return n.tasklet;
841 }
842 return [];
843 },
844
845 jpath : function(jpath, n) {
846 return _flatten(jsonPath.eval(n || this, jpath), true);
847 }
848 };
849
850 rw.trafgen = {
851 startedActual : null, // true or false once server-side state is loaded
852 startedPerceived : false,
853 ratePerceived : 25,
854 rateActual : null, // non-null once server-side state is loaded
855 packetSizePerceived : 1024,
856 packetSizeActual: null
857 };
858
859 rw.trafsim = {
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
864 maxRate: 200000
865 };
866
867 rw.aggregateControlPanel = null;
868
869 rw.theme = {
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)'
885 };
886
887 rw.RateTimer = function(onChange, onStop) {
888 this.rate = 0;
889 this.onChange = onChange;
890 this.onStop = onStop;
891 this.testFrequency = 500;
892 this.testWaveLength = 5000;
893 this.timer = null;
894 return this;
895 };
896
897 rw.RateTimer.prototype = {
898 start: function() {
899 this.rate = 0;
900 if (!this.timer) {
901 this.n = 0;
902 var strategy = this.smoothRateStrategy.bind(this);
903 this.testingStartTime = new Date().getTime();
904 this.timer = setInterval(strategy, this.testFrequency);
905 }
906 },
907
908 stop: function() {
909 if (this.timer) {
910 clearInterval(this.timer);
911 this.timer = null;
912 this.onStop();
913 }
914 },
915
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);
922 this.n += 1;
923 if (this.n >= 10 && this.rate < 1) {
924 this.stop();
925 }
926 }
927 };
928
929 module.exports = rw;