| |
| /* |
| * |
| * Copyright 2016 RIFT.IO Inc |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| // rw.js will no longer be necessary when Angular dependency no longer exists |
| /** |
| * reset values in an array, useful when an array instance is |
| * being observed for changes and simply setting array reference |
| * to a new array instance is not a great option. |
| * |
| * Example: |
| * x = ['a', 'b'] |
| * setValues(x, ['c', 'd']) |
| * x |
| * ['c', 'd'] |
| */ |
| |
| import _isNumber from 'lodash/isNumber'; |
| import _each from 'lodash/each'; |
| import _flatten from 'lodash/flatten'; |
| import _union from 'lodash/union'; |
| |
| var rw = rw || { |
| // Angular specific for now but can be modified in one location if need ever be |
| BaseController: function() { |
| var self = this; |
| |
| // handles factories with detach/cancel methods and listeners with cancel method |
| if (this.$scope) { |
| this.$scope.$on('$stateChangeStart', function() { |
| var properties = Object.getOwnPropertyNames(self); |
| |
| properties.forEach(function(key) { |
| |
| var propertyValue = self[key]; |
| |
| if (propertyValue) { |
| if (Array.isArray(propertyValue)) { |
| propertyValue.forEach(function(item) { |
| if (item.off && typeof item.off == 'function') { |
| item.off(null, null, self); |
| } |
| }); |
| propertyValue.length = 0; |
| } else { |
| if (propertyValue.detached && typeof propertyValue.detached == 'function') { |
| propertyValue.detached(); |
| } else if (propertyValue.cancel && typeof propertyValue.cancel == 'function') { |
| propertyValue.cancel(); |
| } else if (propertyValue.off && typeof propertyValue.off == 'function') { |
| propertyValue.off(null, null, self); |
| } |
| } |
| } |
| }); |
| }); |
| }; |
| |
| // call in to do additional cleanup |
| if (self.doCleanup && typeof self.doCleanup == 'function') { |
| self.doCleanup(); |
| }; |
| }, |
| getSearchParams: function (url) { |
| var a = document.createElement('a'); |
| a.href = url; |
| var params = {}; |
| var items = a.search.replace('?', '').split('&'); |
| for (var i = 0; i < items.length; i++) { |
| if (items[i].length > 0) { |
| var key_value = items[i].split('='); |
| params[key_value[0]] = key_value[1]; |
| } |
| } |
| return params; |
| }, |
| |
| inplaceUpdate : function(ary, values) { |
| var args = [0, ary.length]; |
| Array.prototype.splice.apply(ary, args.concat(values)); |
| } |
| }; |
| |
| // explore making this configurable somehow |
| // api_server = 'http://localhost:5050'; |
| rw.search_params = rw.getSearchParams(window.location.href); |
| // MONKEY PATCHING |
| if (Element.prototype == null) { |
| Element.prototype.uniqueId = 0; |
| } |
| |
| Element.prototype.generateUniqueId = function() { |
| Element.prototype.uniqueId++; |
| return 'uid' + Element.prototype.uniqueId; |
| }; |
| |
| Element.prototype.empty = Element.prototype.empty || function() { |
| while(this.firstChild) { |
| this.removeChild(this.firstChild); |
| } |
| }; |
| |
| /** |
| * Merge one object into another. No circular reference checking so if there |
| * is there might be infinite recursion. |
| */ |
| rw.merge = function(obj1, obj2) { |
| for (prop in obj2) { |
| if (typeof(obj2[prop]) == 'object') { |
| if (prop in obj1) { |
| this.merge(obj1[prop], obj2[prop]); |
| } else { |
| obj1[prop] = obj2[prop]; |
| } |
| } else { |
| obj1[prop] = obj2[prop]; |
| } |
| } |
| } |
| |
| Element.prototype.getElementByTagName = function(tagName) { |
| for (var i = this.children.length - 1; i >= 0; i--) { |
| if (this.children[i].localName == tagName) { |
| return this.children[i]; |
| } |
| } |
| }; |
| |
| rw.ui = { |
| |
| computedWidth : function(elem, defaultValue) { |
| var s = window.getComputedStyle(elem); |
| var w = s['width']; |
| if (w && w != 'auto') { |
| // I've never seen this case, but here anyway |
| return w; |
| } |
| w = s['min-width']; |
| if (w) { |
| return w; |
| } |
| return defaultValue; |
| }, |
| |
| computedHeight : function(elem, defaultValue) { |
| var s = window.getComputedStyle(elem); |
| var w = s['height']; |
| if (w && w != 'auto') { |
| // I've never seen this case, but here anyway |
| return w; |
| } |
| w = s['min-height']; |
| if (w) { |
| return w; |
| } |
| return defaultValue; |
| }, |
| |
| computedStyle : function(elem, property, defaultValue) { |
| var s = window.getComputedStyle(elem); |
| if (s[property]) { |
| return s[property]; |
| } |
| return defaultValue; |
| }, |
| |
| odd : function(n) { |
| return Math.abs(n) % 2 == 1 ? 'odd' : ''; |
| }, |
| |
| status : function(s) { |
| return s == 'OK' ? 'yes' : 'no'; |
| }, |
| |
| capitalize: function(s) { |
| return s ? s.charAt(0).toUpperCase() + s.slice(1) : ''; |
| }, |
| |
| fmt: function(n, fmtStr) { |
| return numeral(n).format(fmtStr); |
| }, |
| |
| // assumes values are in megabytes! |
| bytes: function(n, capacity) { |
| if (n === undefined || isNaN(n)) { |
| return ''; |
| } |
| var units = false; |
| if (capacity === undefined) { |
| capacity = n; |
| units = true; |
| } |
| var suffixes = [ |
| ['KB' , 1000], |
| ['MB' , 1000000], |
| ['GB' , 1000000000], |
| ['TB' , 1000000000000], |
| ['PB' , 1000000000000000] |
| ]; |
| for (var i = 0; i < suffixes.length; i++) { |
| if (capacity < suffixes[i][1]) { |
| return (numeral((n * 1000) / suffixes[i][1]).format('0,0') + (units ? suffixes[i][0] : '')); |
| } |
| } |
| return n + (units ? 'B' : ''); |
| }, |
| |
| // assumes values are already in megabits! |
| bits: function(n, capacity) { |
| if (n === undefined || isNaN(n)) { |
| return ''; |
| } |
| var units = false; |
| if (capacity === undefined) { |
| capacity = n; |
| units = true; |
| } |
| var suffixes = [ |
| ['Mbps' , 1000], |
| ['Gbps' , 1000000], |
| ['Tbps' , 1000000000], |
| ['Pbps' , 1000000000000] |
| ]; |
| for (var i = 0; i < suffixes.length; i++) { |
| if (capacity < suffixes[i][1]) { |
| return (numeral((n * 1000) / suffixes[i][1]).format('0,0') + (units ? suffixes[i][0] : '')); |
| } |
| } |
| return n + (units ? 'Bps' : ''); |
| }, |
| |
| ppsUtilization: function(pps) { |
| return pps ? numeral(pps / 1000000).format('0.0') : ''; |
| }, |
| ppsUtilizationMax: function(item) { |
| var rate = item.rate / 10000; |
| var max = item.max * 0.0015; |
| return rate/max; |
| }, |
| bpsAsPps: function(speed) { |
| return parseInt(speed) * 0.0015; |
| }, |
| |
| upperCase: function(s) { |
| return s.toUpperCase() |
| }, |
| |
| mbpsAsPps: function(mbps) { |
| var n = parseInt(mbps); |
| return isNaN(n) ? 0 : rw.ui.fmt(rw.ui.bpsAsPps(n * 1000000), '0a').toUpperCase(); |
| }, |
| |
| k: function(n) { |
| return rw.ui.fmt(rw.ui.noNaN(n), '0a'); |
| }, |
| |
| noNaN: function(n) { |
| return isNaN(n) ? 0 : n; |
| }, |
| |
| // Labels used in system |
| l10n : { |
| vnf: { |
| 'trafsimclient': 'Traf Sim Client', |
| 'trafsimserver': 'Traf Sim Server', |
| 'ltemmesim': 'MME', |
| 'ltegwsim': 'SAE Gateway', |
| 'trafgen': 'Traf Gen Client', |
| 'trafsink': 'Traf Gen Server', |
| 'loadbal': 'Load Balancer', |
| 'slbalancer': 'Scriptable Load Balancer' |
| } |
| } |
| }; |
| |
| rw.math = { |
| editXml : function(xmlTemplate, domEditor) { |
| var str2dom = new DOMParser(); |
| var dom = str2dom.parseFromString(xmlTemplate, 'text/xml'); |
| if (domEditor) { |
| domEditor(dom); |
| } |
| var dom2str = new XMLSerializer(); |
| return dom2str.serializeToString(dom); |
| }, |
| |
| num : function(el, tag) { |
| return parseInt(this.str(el, tag), 10); |
| }, |
| |
| str : function(el, tag) { |
| var tags = el.getElementsByTagName(tag); |
| return tags.length > 0 ? tags[0].textContent.trim() : ''; |
| }, |
| |
| sum : function(total, i, key, value) { |
| if (_isNumber(value)) { |
| total[key] = (i === 0 ? value : (total[key] + value)); |
| } |
| }, |
| |
| sum2 : function(key) { |
| return function(prev, cur, i) { |
| var value = cur[key]; |
| if (_isNumber(value)) { |
| if (typeof(prev) === 'undefined') { |
| return value; |
| } else { |
| return prev + value; |
| } |
| } |
| return prev; |
| }; |
| }, |
| |
| max : function(key) { |
| return function(prev, cur, i) { |
| var value = cur[key]; |
| if (_isNumber(value)) { |
| if (typeof(prev) === 'undefined') { |
| return value; |
| } else if (prev < value) { |
| return value; |
| } |
| } |
| return prev; |
| }; |
| }, |
| |
| avg2 : function(key) { |
| var sum = rw.math.sum2(key); |
| return function(prev, cur, i, ary) { |
| var s = sum(prev, cur, i); |
| if (i === ary.length - 1) { |
| return s / ary.length; |
| } |
| return s; |
| }; |
| }, |
| |
| avg : function(rows, key) { |
| var total = XmlMath.total(rows, key); |
| return total / rows.length; |
| }, |
| |
| total : function(rows, key) { |
| var total = 0; |
| for (var i = rows.length - 1; i >= 0; i--) { |
| var n = parseInt(rows[i][key]); |
| if (!isNaN(n)) { |
| total += n; |
| } |
| } |
| return total; |
| }, |
| |
| run : function(total, rows, operation) { |
| var i; |
| var f = function(value, key) { |
| operation(total, i, key, value); |
| }; |
| for (i = 0; i < rows.length; i++) { |
| _each(rows[i], f); |
| } |
| } |
| }; |
| |
| |
| rw.db = { |
| open: function (name, onInit, onOpen) { |
| var self = this; |
| |
| var open = window.indexedDB.open(name, 2); |
| |
| open.onerror = function (e) { |
| console.log('Could not open database', name, e.target.error.message); |
| }; |
| |
| open.onsuccess = function (e) { |
| var db = e.target.result; |
| onOpen(db); |
| }; |
| |
| open.onupgradeneeded = function (e) { |
| var db = e.target.result; |
| onInit(db); |
| }; |
| } |
| }; |
| |
| rw.db.Offline = function(name) { |
| this.name = name; |
| this.datastore = 'offline'; |
| }; |
| |
| rw.db.Offline.prototype = { |
| |
| open : function(onOpen) { |
| rw.db.open(this.name, this.init.bind(this), onOpen); |
| }, |
| |
| getItem : function(url, onData) { |
| var self = this; |
| this.open(function(db) { |
| var query = db.transaction(self.datastore) |
| .objectStore(self.datastore) |
| .get(url); |
| query.onsuccess = function(e) { |
| if (e.target.result) { |
| onData(e.target.result.data); |
| } else { |
| console.log('No data found for ' + url + '. You may need to rebuild your offline database'); |
| } |
| } |
| }); |
| }, |
| |
| init : function(db) { |
| var self = this; |
| if (!db.objectStoreNames.contains(this.datastore)) { |
| var create = db.createObjectStore(this.datastore, {keyPath: 'url'}); |
| create.onerror = function(e) { |
| console.log('Could not create object store ' + this.datastore); |
| } |
| } |
| }, |
| |
| saveStore : function(store) { |
| var self = this; |
| this.open(function(db) { |
| for (var i = 0; i < store.length; i++) { |
| var save = db.transaction(self.datastore, "readwrite") |
| .objectStore(self.datastore) |
| .put(store[i]); |
| } |
| }); |
| } |
| }; |
| |
| rw.api = { |
| nRequests : 0, |
| tRequestsMs: 0, |
| tRequestMaxMs: 0, |
| nRequestsByUrl: {}, |
| statsId: null, |
| |
| resetStats: function() { |
| rw.api.nRequests = 0; |
| rw.api.tRequestsMs = 0; |
| rw.api.tRequestMaxMs = 0; |
| rw.api.nRequestsByUrl = {}; |
| }, |
| |
| handleAjaxError : function (req, status, err) { |
| console.log('failed', req, status, err); |
| }, |
| |
| json: function(url) { |
| return this.get(url, 'application/json') |
| }, |
| |
| get: function(url, accept) { |
| var deferred = jQuery.Deferred(); |
| if (rw.api.offline) { |
| rw.api.offline.getItem(url, function (data) { |
| deferred.resolve(data); |
| }); |
| } else { |
| var startTime = new Date().getTime(); |
| jQuery.ajax(rw.api.server + url, { |
| type: 'GET', |
| dataType: 'json', |
| error: rw.api.handleAjaxError, |
| headers: rw.api.headers(accept), |
| success: function (data) { |
| rw.api.recordUrl(url, startTime); |
| deferred.resolve(data); |
| }, |
| error: function(e) { |
| deferred.reject(e); |
| } |
| }); |
| } |
| return deferred.promise(); |
| }, |
| |
| headers: function(accept, contentType) { |
| var h = { |
| Accept: accept |
| }; |
| if (rw.api.statsId != null) { |
| h['x-stats'] = rw.api.statsId; |
| } |
| if (contentType) { |
| h['Content-Type'] = contentType; |
| } |
| return h; |
| }, |
| |
| recordUrl:function(url, startTime) { |
| var elapsed = new Date().getTime() - startTime; |
| rw.api.tRequestsMs += elapsed; |
| rw.api.nRequests += 1; |
| rw.api.tRequestMaxMs = Math.max(rw.api.tRequestMaxMs, elapsed); |
| if (url in rw.api.nRequestsByUrl) { |
| var metric = rw.api.nRequestsByUrl[url]; |
| metric.url = url; |
| metric.n += 1; |
| metric.max = Math.max(metric.max, elapsed); |
| } else { |
| rw.api.nRequestsByUrl[url] = {url: url, n: 1, max: elapsed}; |
| } |
| }, |
| |
| put: function(url, data, contentType) { |
| return this.push('PUT', url, data, contentType); |
| }, |
| |
| post: function(url, data, contentType) { |
| return this.push('POST', url, data, contentType); |
| }, |
| |
| rpc: function(url, data, error) { |
| if(error === undefined){ |
| error = function(a,b,c){ |
| } |
| } |
| return this.push('POST', url, data, 'application/vnd.yang.data+json', error); |
| }, |
| |
| push: function(method, url, data, contentType, errorFn) { |
| var deferred = jQuery.Deferred(); |
| if (rw.api.offline) { |
| // eating offline put request |
| if(contentType == 'application/vnd.yang.data+json'){ |
| var payload = data; |
| rw.api.offline.getItem(url, function (data) { |
| deferred.resolve(data); |
| }); |
| } |
| deferred.resolve({}); |
| } else { |
| var startTime = new Date().getTime(); |
| jQuery.ajax(rw.api.server + url, { |
| type: method, |
| error: rw.api.handleAjaxError, |
| dataType: 'json', |
| headers: rw.api.headers('application/json', contentType), |
| data: JSON.stringify(data), |
| success: function (response) { |
| rw.api.recordUrl(url, startTime); |
| deferred.resolve(response); |
| }, |
| error: errorFn |
| }); |
| } |
| return deferred.promise(); |
| }, |
| |
| setOffline: function(name) { |
| if (name) { |
| rw.api.offline = new rw.db.Offline(name); |
| } else { |
| rw.api.offline = false; |
| } |
| }, |
| |
| // When passing things to ConfD ('/api/...') then '/' needs to be |
| // %252F(browser) --> %2F(Rest) --> / ConfD |
| encodeUrlParam: function(value) { |
| var once = rw.api.singleEncodeUrlParam(value); |
| return once.replace(/%/g, '%25'); |
| }, |
| |
| // UrlParam cannot have '/' and encoding it using %2F gets double-encoded in flask |
| singleEncodeUrlParam: function(value) { |
| return value.replace(/\//g, '%2F'); |
| } |
| }; |
| |
| rw.api.SocketSubscriber = function(url) { |
| this.url = url; |
| |
| this.id = ++rw.api.SocketSubscriber.uniqueId; |
| |
| this.subscribed = false; |
| this.offlineRateMs = 2000; |
| }, |
| |
| rw.api.SocketSubscriber.uniqueId = 0; |
| |
| rw.api.SocketSubscriber.prototype = { |
| |
| // does not support PUT/PORT with payloads requests yet |
| websubscribe : function(webUrl, onload, offline) { |
| this.subscribeMeta(onload, { |
| url: webUrl |
| }, offline); |
| }, |
| |
| subscribeMeta : function(onload, meta, offline) { |
| var self = this; |
| |
| if (this.subscribed) { |
| this.unsubscribe(); |
| } |
| |
| var m = meta || {}; |
| if (rw.api.offline) { |
| this.subscribeOffline(onload, m, offline); |
| } else { |
| this.subscribeOnline(onload, m); |
| } |
| }, |
| |
| subscribeOffline: function(onload, meta, offline) { |
| var self = this; |
| rw.api.json(meta.url).done(function(data) { |
| var _update = function() { |
| if (offline) { |
| offline(data); |
| } else { |
| onload(data); |
| } |
| }; |
| |
| this.offlineTimer = setInterval(_update, self.offlineRateMs); |
| }); |
| }, |
| |
| subscribeOnline: function(onload, meta) { |
| var self = this; |
| var _subscribe = function() { |
| meta.widgetId = self.id; |
| meta.accept = meta.accept || 'application/json'; |
| document.websocket().emit(self.url, meta); |
| self.subscribed = true; |
| }; |
| |
| var _register = function() { |
| document.websocket().on(self.url + '/' + self.id, function(dataString) { |
| var data = dataString; |
| |
| // auto convert to object to make backward compatible |
| if (meta.accept.match(/[+\/]json$/) && dataString != '') { |
| data = JSON.parse(dataString); |
| } |
| onload(data); |
| }); |
| _subscribe(); |
| }; |
| document.websocket().on('error',function(d){ |
| console.log('socket error', d) |
| }); |
| document.websocket().on('close',function(d){ |
| console.log('socket close', d) |
| }) |
| document.websocket().on('connect', _register); |
| |
| // it's possible this call is not nec. and will always be called |
| // as part of connect statement above |
| _register(); |
| }, |
| |
| unsubscribe : function() { |
| if (rw.api.offline) { |
| if (this.offlineTimer) { |
| clearInterval(this.offlineTimer); |
| this.offlineTimer = null; |
| } |
| } else { |
| var unsubscribe = { widgetId: this.id, enable: false }; |
| document.websocket().emit(this.url, unsubscribe); |
| this.subscribed = false; |
| } |
| } |
| }; |
| |
| rw.api.server = rw.search_params['api_server'] || ''; |
| |
| document.websocket = function() { |
| if ( ! this.socket ) { |
| //io.reconnection = true; |
| var wsServer = rw.api.server || 'http://' + document.domain + ':' + location.port; |
| var wsUrl = wsServer + '/rwapp'; |
| this.socket = io.connect(wsUrl, {reconnection:true}); |
| } |
| return this.socket; |
| }; |
| |
| rw.api.setOffline(rw.search_params['offline']); |
| |
| rw.vnf = { |
| ports: function(service) { |
| return _flatten(jsonPath.eval(service, '$.connector[*].interface[*].port')); |
| }, |
| fabricPorts: function(service) { |
| return _flatten(jsonPath.eval(service, '$.vm[*].fabric.port')); |
| } |
| }; |
| |
| rw.VcsVisitor = function(enter, leave) { |
| this.enter = enter; |
| this.leave = leave; |
| } |
| |
| rw.VcsVisitor.prototype = { |
| |
| visit: function(node, parent, i, listType) { |
| var hasChildren = this.enter(parent, node, i, listType); |
| if (hasChildren) { |
| switch (rw.vcs.nodeType(node)) { |
| case 'rwsector': |
| this.visitChildren(node, node.collection, 'collection'); |
| break; |
| case 'rwcolony': |
| this.visitChildren(node, node.collection, 'collection'); |
| this.visitChildren(node, node.vm, 'vm'); |
| break; |
| case 'rwcluster': |
| this.visitChildren(node, node.vm, 'vm'); |
| break; |
| case 'RWVM': |
| this.visitChildren(node, node.process, 'process'); |
| break; |
| case 'RWPROC': |
| this.visitChildren(node, node.tasklet, 'tasklet'); |
| break; |
| } |
| } |
| if (this.leave) { |
| this.leave(parent, node, obj, i, listType); |
| } |
| }, |
| |
| visitChildren : function(parent, children, listType) { |
| if (!children) { |
| return; |
| } |
| var i = 0; |
| var self = this; |
| _each(children, function(child) { |
| self.visit.call(self, child, parent, i, listType); |
| i += 1; |
| }); |
| } |
| }; |
| |
| rw.vcs = { |
| |
| allVms : function() { |
| return _flatten([this.jpath('$.collection[*].vm'), this.jpath('$.collection[*].collection[*].vm')], true); |
| }, |
| |
| vms: function(n) { |
| if (n == undefined || n === null || n === this) { |
| return this.allVms(); |
| } |
| switch (rw.vcs.nodeType(n)) { |
| case 'rwcolony': |
| return this.jpath('$.collection[*].vm[*]', n); |
| case 'rwcluster': |
| return this.jpath('$.vm[*]', n); |
| case 'RWVM': |
| return [n]; |
| default: |
| return null; |
| } |
| }, |
| |
| nodeType: function(node) { |
| if (node.component_type === 'RWCOLLECTION') { |
| return node.collection_info['collection-type']; |
| } |
| return node.component_type; |
| }, |
| |
| allClusters : function() { |
| return this.jpath('$.collection[*].collection'); |
| }, |
| |
| allColonies: function() { |
| return this.jpath('$.collection'); |
| }, |
| |
| allPorts:function(n) { |
| switch (rw.vcs.nodeType(n)) { |
| case 'rwsector': |
| return this.jpath('$.collection[*].collection[*].vm[*].port[*]', n); |
| case 'rwcolony': |
| return this.jpath('$.collection[*].vm[*].port[*]', n); |
| case 'rwcluster': |
| return this.jpath('$.vm[*].port[*]', n); |
| case 'RWVM': |
| return this.jpath('$.port[*]', n); |
| default: |
| return null; |
| } |
| }, |
| |
| allFabricPorts:function(n) { |
| switch (rw.vcs.nodeType(n)) { |
| case 'rwsector': |
| return this.jpath('$.collection[*].collection[*].vm[*].fabric.port[*]', n); |
| case 'rwcolony': |
| return this.jpath('$.collection[*].vm[*].fabric.port[*]', n); |
| case 'rwcluster': |
| return this.jpath('$.vm[*].fabric.port[*]', n); |
| case 'RWVM': |
| return this.jpath('$.fabric.port[*]', n); |
| default: |
| return null; |
| } |
| }, |
| |
| getChildren: function(n) { |
| switch (rw.vcs.nodeType(n)) { |
| case 'rwcolony': |
| return 'vm' in n ? _union(n.collection, n.vm) : n.collection; |
| case 'rwcluster': |
| return n.vm; |
| case 'RWVM': |
| return n.process; |
| case 'RWPROC': |
| return n.tasklet; |
| } |
| return []; |
| }, |
| |
| jpath : function(jpath, n) { |
| return _flatten(jsonPath.eval(n || this, jpath), true); |
| } |
| }; |
| |
| rw.trafgen = { |
| startedActual : null, // true or false once server-side state is loaded |
| startedPerceived : false, |
| ratePerceived : 25, |
| rateActual : null, // non-null once server-side state is loaded |
| packetSizePerceived : 1024, |
| packetSizeActual: null |
| }; |
| |
| rw.trafsim = { |
| startedActual : null, // true or false once server-side state is loaded |
| startedPerceived : false, |
| ratePerceived : 50000, |
| rateActual : null, // non-null once server-side state is loaded |
| maxRate: 200000 |
| }; |
| |
| rw.aggregateControlPanel = null; |
| |
| rw.theme = { |
| // purple-2 and blue-2 |
| txBps : 'hsla(212, 57%, 50%, 1)', |
| txBpsTranslucent : 'hsla(212, 57%, 50%, 0.7)', |
| rxBps : 'hsla(212, 57%, 50%, 1)', |
| rxBpsTranslucent : 'hsla(212, 57%, 50%, 0.7)', |
| txPps : 'hsla(260, 35%, 50%, 1)', |
| txPpsTranslucent : 'hsla(260, 35%, 50%, 0.7)', |
| rxPps : 'hsla(260, 35%, 50%, 1)', |
| rxPpsTranslucent : 'hsla(260, 35%, 50%, 0.7)', |
| memory : 'hsla(27, 98%, 57%, 1)', |
| memoryTranslucent : 'hsla(27, 98%, 57%, 0.7)', |
| cpu : 'hsla(123, 45%, 50%, 1)', |
| cpuTranslucent : 'hsla(123, 45%, 50%, 0.7)', |
| storage : 'hsla(180, 78%, 25%, 1)', |
| storageTranslucent : 'hsla(180, 78%, 25%, 0.7)' |
| }; |
| |
| rw.RateTimer = function(onChange, onStop) { |
| this.rate = 0; |
| this.onChange = onChange; |
| this.onStop = onStop; |
| this.testFrequency = 500; |
| this.testWaveLength = 5000; |
| this.timer = null; |
| return this; |
| }; |
| |
| rw.RateTimer.prototype = { |
| start: function() { |
| this.rate = 0; |
| if (!this.timer) { |
| this.n = 0; |
| var strategy = this.smoothRateStrategy.bind(this); |
| this.testingStartTime = new Date().getTime(); |
| this.timer = setInterval(strategy, this.testFrequency); |
| } |
| }, |
| |
| stop: function() { |
| if (this.timer) { |
| clearInterval(this.timer); |
| this.timer = null; |
| this.onStop(); |
| } |
| }, |
| |
| smoothRateStrategy: function() { |
| var x = (new Date().getTime() - this.testingStartTime) / this.testWaveLength; |
| this.rate = Math.round(100 * 0.5 * (1 - Math.cos(x))); |
| // in theory you could use wavelength and frequency to determine stop but this |
| // is guaranteed to stop at zero. |
| this.onChange(this.rate); |
| this.n += 1; |
| if (this.n >= 10 && this.rate < 1) { |
| this.stop(); |
| } |
| } |
| }; |
| |
| module.exports = rw; |