diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css b/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css index a9ba89e04e..34422f7bd6 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css +++ b/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css @@ -293,35 +293,45 @@ /* TODO: Review for not-permitted links */ #ov-topo2 svg .link.not-permitted { stroke: rgb(255,0,0); - stroke-width: 5.0; stroke-dasharray: 8 4; } #ov-topo2 svg .link.secondary { - stroke-width: 3px; stroke: rgba(0,153,51,0.5); } +#ov-topo2 svg .link.secondary.port-traffic-green { + stroke: rgb(0,153,51); +} + +#ov-topo2 svg .link.secondary.port-traffic-yellow { + stroke: rgb(128,145,27); +} + +#ov-topo2 svg .link.secondary.port-traffic-orange { + stroke: rgb(255, 137, 3); +} + +#ov-topo2 svg .link.secondary.port-traffic-red { + stroke: rgb(183, 30, 21); +} + /* Port traffic color visualization for Kbps, Mbps, and Gbps */ #ov-topo2 svg .link.secondary.port-traffic-Kbps { stroke: rgb(0,153,51); - stroke-width: 5.0; } #ov-topo2 svg .link.secondary.port-traffic-Mbps { stroke: rgb(128,145,27); - stroke-width: 6.5; } #ov-topo2 svg .link.secondary.port-traffic-Gbps { stroke: rgb(255, 137, 3); - stroke-width: 8.0; } #ov-topo2 svg .link.secondary.port-traffic-Gbps-choked { stroke: rgb(183, 30, 21); - stroke-width: 8.0; } diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js b/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js index c3662e6257..1166fb8e4f 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js @@ -48,12 +48,14 @@ addModel: function (data) { if (Object.getPrototypeOf(data) !== Object.prototype) { this.models.push(data); + data.collection = this; this._byId[data.get('id')] = data; return data; } var CollectionModel = this.model; var model = new CollectionModel(data, this); + model.collection = this; this.models.push(model); this._byId[data.id] = model; @@ -96,6 +98,12 @@ filter: function (comparator) { return _.filter(this.models, comparator); }, + empty: function () { + _.map(this.models, function (m) { + m.remove(); + }); + this._reset(); + }, _reset: function () { this._byId = []; this.models = []; @@ -111,7 +119,6 @@ .factory('Topo2Collection', ['Topo2Model', 'FnService', function (_Model_, fn) { - Collection.extend = fn.extend; Model = _Model_; return Collection; diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Event.js b/web/gui/src/main/webapp/app/view/topo2/topo2Event.js index a03dab4d9b..e8b8eac0de 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Event.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Event.js @@ -27,7 +27,7 @@ 'use strict'; // injected refs - var $log, wss, t2fs; + var $log, wss, t2fs, t2ovs; // internal state var handlerMap, @@ -42,7 +42,8 @@ topo2CurrentRegion: t2fs, topo2PeerRegions: t2fs, - topo2UiModelEvent: t2fs + topo2UiModelEvent: t2fs, + topo2Highlights: t2ovs.showHighlights, // Add further event names / module references as needed }; @@ -83,12 +84,13 @@ angular.module('ovTopo2') .factory('Topo2EventService', [ - '$log', 'WebSocketService', 'Topo2ForceService', + '$log', 'WebSocketService', 'Topo2ForceService', 'Topo2OverlayService', - function (_$log_, _wss_, _t2fs_) { + function (_$log_, _wss_, _t2fs_, _t2ovs_) { $log = _$log_; wss = _wss_; t2fs = _t2fs_; + t2ovs = _t2ovs_; // deferred creation of handler map, so module references are good createHandlerMap(); diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Label.js b/web/gui/src/main/webapp/app/view/topo2/topo2Label.js index 8a474246c5..28f134c2d6 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Label.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Label.js @@ -50,26 +50,39 @@ _iconG: {}, _labelG: {}, - initialize: function (data, node, options) { + initialize: function (data, node) { this.parent = node; - this.options = options || {}; - t2zs.addZoomEventListener(this.setScale.bind(this)); + this.beforeRender(); this.render(); + this.afterRender(); }, - onChange: function (property, value, options) { + onChange: function (property) { if (property === 'x' || property === 'y') { this._position(); } + + if (property === 'label') { + var width = this._labelG.text.node().getBBox().width + 20, + height = this._labelG.text.node().getBBox().height + 10; + + this._labelG.text.text(this.get('label')); + this._labelG.rect.attr({ + width: width, + height: height + }).style({ + transform: sus.translate(-(width/2) + 'px', -(height/2) + 'px') + }); + } }, setPosition: function () {}, setScale: function () {}, applyStyles: function () { - var styles = _.extend({}, defaultStyles, this.get('styles')); + var styles = _.extend({}, defaultStyles, this.get('styles') || {}); - if (this.get('text')) { + if (this.get('label')) { this._labelG.text.style(styles.label.text); this._labelG.rect.style(styles.label.rect); } @@ -79,21 +92,17 @@ this._iconG.rect.style(styles.icon.rect); } }, - _position: function () { this.el.style('transform', sus.translate(this.get('x') + 'px', this.get('y') + 'px')); }, - labelDimensions: function () { - return this.content.node().getBBox(); - }, renderText: function () { this._labelG.el = this.content.append('g') .attr('class', 'label-group'); this._labelG.rect = this._labelG.el.append('rect'); this._labelG.text = this._labelG.el.append('text') - .text(this.get('text')) + .text(this.get('label')) .attr('y', '0.4em') .style('text-anchor', 'middle'); @@ -127,6 +136,7 @@ transform: sus.translate(iconX, iconY) }); }, + beforeRender: function () {}, render: function () { this.el = this.parent.append('g') .attr('class', 'topo2-label') @@ -146,6 +156,10 @@ this.applyStyles(); this.setPosition(); this.setScale(); + }, + afterRender: function () {}, + remove: function () { + this.el.remove(); } }); } diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2LabelCollection.js b/web/gui/src/main/webapp/app/view/topo2/topo2LabelCollection.js index 895f4910ff..ba05bb5b31 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2LabelCollection.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2LabelCollection.js @@ -19,7 +19,6 @@ A collection of any type of label (Topo2Label, Topo2Badge, Topo2LinkLabel) */ - (function () { var instance; @@ -32,12 +31,18 @@ var LabelCollection = Collection.extend({ initialize: function () { instance = this; + }, + addLabel: function (Model, label, targetNode, options) { + if (this._byId[label.id]) { + this.get(label.id).set(label); + } else { + var lab = new Model(label, targetNode, options) + this.add(lab); + } } }); return instance || new LabelCollection(); } ]); - - -})(); \ No newline at end of file +})(); diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js index 77892ab6a7..eb0da8f5d4 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js @@ -22,7 +22,8 @@ (function () { 'use strict'; - var $log, Collection, Model, ts, sus, t2zs, t2vs, t2lps, fn, ps, t2mss; + var $log, Collection, Model, ts, sus, t2zs, t2vs, t2lps, + fn, ps, t2mss, t2ts; var linkLabelOffset = '0.35em'; @@ -134,7 +135,8 @@ enhanced: this.get('enhanced'), selected: this.get('selected'), suppressedmax: this.get('mastership') - } + }, + (this.linkLabel) ? this.linkLabel.linkLabelCSSClass() : null ); }, expected: function () { @@ -154,6 +156,7 @@ // Update class names when the model changes if (this.el) { this.el.attr('class', this.svgClassName()); + this.setScale(); } }, enhance: function () { @@ -239,7 +242,6 @@ }; }, setPosition: function () { - var multiline = this.get('multiline'); if (multiline) { var offsetAmt = this.amt(multiline.deviceLinks, multiline.index); @@ -256,6 +258,9 @@ this.el.attr(this.get('position')); } + if (this.linkLabel) { + this.linkLabel.setPosition(); + } }, updatePortPosition: function () { var sourcePos = this.locatePortLabel(1), @@ -324,11 +329,39 @@ this.setVisibility(); this.setScale(); }, + linkWidth: function () { + var width = widthRatio; + if (this.get('enhanced')) { width = 3.5; } + if (this.linkLabel) { + var scale = d3.scale.ordinal() + .rangeRoundPoints([4, 8]), + label = this.linkLabel.get('label').split(' '); + + switch (t2ts.selectedTrafficOverlay()) { + case 'flowStatsBytes': + scale.domain(['KB', 'MB', 'GB']); + width = scale(label[1]); + break; + case 'portStatsBitSec': + scale.domain(['Kbps', 'Mbps', 'Gbps']) + width = scale(label[1]); + break; + case 'portStatsPktSec': + scale = d3.scale.linear() + .domain([1, 10, 100, 1000, 10000]) + .range(d3.range(3.5, 9)) + .clamp(true); + width = scale(parseInt(label[0])); + } + } + + return width; + }, setScale: function () { if (!this.el) return; - var linkWidthRatio = this.get('enhanced') ? 3.5 : widthRatio; + var linkWidthRatio = this.linkWidth(); var width = linkScale(linkWidthRatio) / t2zs.scale(); this.el.attr('stroke-width', width + 'px'); @@ -342,6 +375,9 @@ this.setPosition(); + if (this.linkLabel) { + this.linkLabel.setScale(); + } }, update: function () { if (this.get('enhanced')) { @@ -390,9 +426,9 @@ '$log', 'Topo2Collection', 'Topo2Model', 'ThemeService', 'SvgUtilService', 'Topo2ZoomService', 'Topo2ViewService', 'Topo2LinkPanelService', 'FnService', 'PrefsService', - 'Topo2MastershipService', + 'Topo2MastershipService', 'Topo2TrafficService', function (_$log_, _c_, _Model_, _ts_, _sus_, - _t2zs_, _t2vs_, _t2lps_, _fn_, _ps_, _t2mss_) { + _t2zs_, _t2vs_, _t2lps_, _fn_, _ps_, _t2mss_, _t2ts_) { $log = _$log_; ts = _ts_; @@ -405,6 +441,7 @@ fn = _fn_; ps = _ps_; t2mss = _t2mss_; + t2ts = _t2ts_; return { createLinkCollection: createLinkCollection diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2LinkLabel.js b/web/gui/src/main/webapp/app/view/topo2/topo2LinkLabel.js index 6e6d7fae69..66b06e54f8 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2LinkLabel.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2LinkLabel.js @@ -30,8 +30,21 @@ className: 'topo2-linklabel', maxHeight: 30, minHeight: 20, + initialize: function (label, dom, options) { + this.link = options.link; + this.parent = dom; + this.super = this.constructor.__super__; + this.super.initialize.apply(this, arguments); + }, + onChange: function () { + this.link.onChange(); + this.constructor.__super__.onChange.apply(this, arguments); + }, + linkLabelCSSClass: function () { + return this.get('css') || ''; + }, setPosition: function () { - var link = this.options.link; + var link = this.link; this.set({ x: (link.source.x + link.target.x) / 2, y: (link.source.y + link.target.y) / 2 @@ -40,6 +53,14 @@ setScale: function () { this.content.style('transform', 'scale(' + t2zs.adjustmentScale(20, 30) + ')'); + }, + beforeRender: function () { + this.link.linkLabel = this; + }, + remove: function () { + this.link.linkLabel = null; + this.link.onChange(); + this.constructor.__super__.remove.apply(this, arguments); } }); } diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Model.js b/web/gui/src/main/webapp/app/view/topo2/topo2Model.js index 31ec0f6741..efcd201153 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Model.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Model.js @@ -23,13 +23,8 @@ Visualization of the topology in an SVG layer, using a D3 Force Layout. 'use strict'; function Model(attributes, collection) { - - var attrs = attributes || {}; this.attributes = {}; - - attrs = angular.extend({}, attrs); - this.set(attrs, { silent: true }); - this.collection = collection; + this.set(angular.extend({}, attributes || {}), { silent: true }); this.initialize.apply(this, arguments); } @@ -116,6 +111,11 @@ Visualization of the topology in an SVG layer, using a D3 Force Layout. }, toJSON: function (options) { return angular.copy(this.attributes); + }, + remove: function () { + if (this.collection) { + this.collection.remove(this); + } } }; diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Overlay.js b/web/gui/src/main/webapp/app/view/topo2/topo2Overlay.js index 808ee69a3b..1227c307e3 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Overlay.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Overlay.js @@ -22,7 +22,7 @@ var t2os = 'Topo2OverlayService: '; // injected refs - var $log, $timeout, fs, gs, wss, t2kcs, api; + var $log, $timeout, fs, gs, wss, t2kcs, t2rs, t2lc, api, LinkLabel; // internal state var overlays = {}, @@ -134,27 +134,20 @@ } function showHighlights(data) { - function doHighlight() { - _showHighlights(data); - } - - // note: this allows the server-side event to add a manual delay - // before invoking the highlight... this was (originally) to - // allow for the re-creation of the DOM model, before trying - // to reference elements. For Topo2, there may be a better - // solution, making this piece of code redundant. Steven?? - - if (data.delay) { - $timeout(doHighlight, data.delay); - } else { - doHighlight(); - } - - } - - function _showHighlights(data) { - // TODO: implement the highlighting .. see topoOverlay.js for example $log.info('+++ TOPO 2 +++ show highlights', data); + t2lc.empty(); + var linkLabelsDOM = d3.select('.topo2-linkLabels'); + _.each(data.links, function (link) { + // TODO: Inconsistent host id's (currentRegion and LinkLabel) + var id = link.id.replace('/None/0', '/None').replace('-', '~'), + lab = t2rs.getLink(id); + // TODO: There's a bug in backend where link id is in reverse + if (lab) { + t2lc.addLabel(LinkLabel, link, linkLabelsDOM, { + link: lab + }); + } + }); } // ======================================================================== @@ -162,15 +155,20 @@ angular.module('ovTopo2') .factory('Topo2OverlayService', [ '$log', '$timeout', 'FnService', 'GlyphService', 'WebSocketService', - 'Topo2KeyCommandService', + 'Topo2KeyCommandService', 'Topo2RegionService', 'Topo2LabelCollection', + 'Topo2LinkLabel', - function (_$log_, _$timeout_, _fs_, _gs_, _wss_, _t2kcs_) { + function (_$log_, _$timeout_, _fs_, _gs_, _wss_, _t2kcs_, _t2rs_, + _t2lc_, _t2ll_) { $log = _$log_; $timeout = _$timeout_; fs = _fs_; gs = _gs_; wss = _wss_; t2kcs = _t2kcs_; + t2rs = _t2rs_; + t2lc = _t2lc_; + LinkLabel = _t2ll_; return { register: register, diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Traffic.js b/web/gui/src/main/webapp/app/view/topo2/topo2Traffic.js index 6ca4d5ec41..1cb703f5b5 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Traffic.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Traffic.js @@ -35,6 +35,7 @@ // internal state var mode = null, + currentIndex = 0, allIndex = 0; // === ----------------------------------------------------- @@ -59,9 +60,14 @@ trafficType: allTrafficTypes[allIndex] }); flash.flash(allTrafficMsgs[allIndex]); + currentIndex = allIndex; allIndex = (allIndex + 1) % 3; } + function selectedTrafficOverlay() { + return allTrafficTypes[currentIndex]; + } + // === ----------------------------------------------------- angular.module('ovTopo2') @@ -80,7 +86,8 @@ // invoked from toolbar overlay buttons or keystrokes cancelTraffic: cancelTraffic, - showAllTraffic: showAllTraffic + showAllTraffic: showAllTraffic, + selectedTrafficOverlay: selectedTrafficOverlay } } ]);