diff --git a/web/gui/src/main/webapp/app/fw/util/fn.js b/web/gui/src/main/webapp/app/fw/util/fn.js index 33ad2f666e..0d5d4cc658 100644 --- a/web/gui/src/main/webapp/app/fw/util/fn.js +++ b/web/gui/src/main/webapp/app/fw/util/fn.js @@ -413,6 +413,29 @@ return classes.join(' '); } + function extend(protoProps, staticProps) { + + var parent = this, + child; + + child = function () { + return parent.apply(this, arguments); + }; + + angular.extend(child, parent, staticProps); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function and add the prototype properties. + child.prototype = angular.extend({}, parent.prototype, protoProps); + child.prototype.constructor = child; + + // Set a convenience property in case the parent's prototype is needed + // later. + child.__super__ = parent.prototype; + + return child; + } + angular.module('onosUtil') .factory('FnService', @@ -452,7 +475,8 @@ addToTrie: addToTrie, removeFromTrie: removeFromTrie, trieLookup: trieLookup, - classNames: classNames + classNames: classNames, + extend: extend }; }]); diff --git a/web/gui/src/main/webapp/app/fw/widget/listBuilder.js b/web/gui/src/main/webapp/app/fw/widget/listBuilder.js new file mode 100644 index 0000000000..113cb00533 --- /dev/null +++ b/web/gui/src/main/webapp/app/fw/widget/listBuilder.js @@ -0,0 +1,60 @@ +/* + * Copyright 2015-present Open Networking Laboratory + * + * 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. + */ + +/* + ONOS GUI -- Widget -- List Service + */ + +(function () { + 'use strict'; + + function addProp(el, label, value) { + var tr = el.append('tr'), + lab; + if (typeof label === 'string') { + lab = label.replace(/_/g, ' '); + } else { + lab = label; + } + + function addCell(cls, txt) { + tr.append('td').attr('class', cls).html(txt); + } + + addCell('label', lab + ' :'); + addCell('value', value); + } + + function addSep(el) { + el.append('tr').append('td').attr('colspan', 2).append('hr'); + } + + function listProps(el, data) { + data.propOrder.forEach(function (p) { + if (p === '-') { + addSep(el); + } else { + addProp(el, p, data.props[p]); + } + }); + } + + angular.module('onosWidget') + .factory('ListService', [ + function () { + return listProps; + }]); +}()); 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 540fe5b9f7..4a7a44a300 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 @@ -181,7 +181,7 @@ } -#ov-topo2 svg .node.device.selected .node-container { +#ov-topo2 svg .node.selected .node-container { stroke-width: 2.0; stroke: #009fdb; } 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 11953e1f4f..ab684eb70b 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js @@ -22,7 +22,8 @@ (function () { 'use strict'; - var Model; + var Model, + extend; function Collection(models, options) { @@ -91,34 +92,12 @@ } }; - Collection.extend = function (protoProps, staticProps) { - - var parent = this; - var child; - - child = function () { - return parent.apply(this, arguments); - }; - - angular.extend(child, parent, staticProps); - - // Set the prototype chain to inherit from `parent`, without calling - // `parent`'s constructor function and add the prototype properties. - child.prototype = angular.extend({}, parent.prototype, protoProps); - child.prototype.constructor = child; - - // Set a convenience property in case the parent's prototype is needed - // later. - child.__super__ = parent.prototype; - - return child; - }; - angular.module('ovTopo2') .factory('Topo2Collection', - ['Topo2Model', - function (_Model_) { + ['Topo2Model', 'FnService', + function (_Model_, fn) { + Collection.extend = fn.extend; Model = _Model_; return Collection; } diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2DetailsPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2DetailsPanel.js new file mode 100644 index 0000000000..3bea0bc0ac --- /dev/null +++ b/web/gui/src/main/webapp/app/view/topo2/topo2DetailsPanel.js @@ -0,0 +1,63 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * 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. + */ + +/* + ONOS GUI -- Topology View Module. + Module that displays the details panel for selected nodes + */ + +(function () { + 'use strict'; + + // Injected Services + var Panel; + + // Internal State + var detailsPanel; + + // configuration + var id = 'topo2-p-detail', + className = 'topo-p', + panelOpts = { + width: 260 // summary and detail panel width + }; + + function getInstance() { + if (detailsPanel) { + return detailsPanel; + } + + var options = angular.extend({}, panelOpts, { + class: className + }); + + detailsPanel = new Panel(id, options); + detailsPanel.el.classed(className, true); + + return detailsPanel; + } + + angular.module('ovTopo2') + .factory('Topo2DetailsPanelService', + ['Topo2PanelService', + function (_ps_) { + + Panel = _ps_; + + return getInstance; + } + ]); +})(); diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Device.js b/web/gui/src/main/webapp/app/view/topo2/topo2Device.js index 19d55549f2..9b82c901d0 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Device.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Device.js @@ -74,25 +74,7 @@ }, onClick: function () { - var ev = d3.event; - - if (ev.shiftKey) { - // TODO: Multi-Select Details Panel - this.set('selected', true); - } else { - - var s = Boolean(this.get('selected')); - // Clear all selected Items - _.each(this.collection.models, function (m) { - m.set('selected', false); - }); - - this.set('selected', !s); - } - - var selected = this.collection.filter(function (m) { - return m.get('selected'); - }); + var selected = this.select(d3.event); if (_.isArray(selected) && selected.length > 0) { if (selected.length === 1) { diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2DeviceDetailsPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2DeviceDetailsPanel.js index 624b234920..741f942af4 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2DeviceDetailsPanel.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2DeviceDetailsPanel.js @@ -23,17 +23,13 @@ 'use strict'; // Injected Services - var Panel, gs, wss, flash, bs, fs, ns; + var Panel, gs, wss, flash, bs, fs, ns, listProps; // Internal State var detailsPanel; // configuration var id = 'topo2-p-detail', - className = 'topo-p', - panelOpts = { - width: 260 // summary and detail panel width - }, handlerMap = { 'showDetails': showDetails }; @@ -69,43 +65,7 @@ function init() { bindHandlers(); - - var options = angular.extend({}, panelOpts, { - class: className - }); - - detailsPanel = new Panel(id, options); - detailsPanel.p.classed(className, true); - } - - function addProp(tbody, label, value) { - var tr = tbody.append('tr'), - lab; - if (typeof label === 'string') { - lab = label.replace(/_/g, ' '); - } else { - lab = label; - } - - function addCell(cls, txt) { - tr.append('td').attr('class', cls).html(txt); - } - addCell('label', lab + ' :'); - addCell('value', value); - } - - function addSep(tbody) { - tbody.append('tr').append('td').attr('colspan', 2).append('hr'); - } - - function listProps(tbody, data) { - data.propOrder.forEach(function (p) { - if (p === '-') { - addSep(tbody); - } else { - addProp(tbody, p, data.props[p]); - } - }); + detailsPanel = Panel(); } function addBtnFooter() { @@ -136,10 +96,6 @@ cb: function () { ns.navTo(path, { devId: devId }); } }); } - // TODO: Implement Overlay service - // else if (btn = _getButtonDef(id, data)) { - // addAction(btn); - // } }); } @@ -196,17 +152,17 @@ } function toggle() { - var on = detailsPanel.p.toggle(), + var on = detailsPanel.el.toggle(), verb = on ? 'Show' : 'Hide'; flash.flash(verb + ' Details Panel'); } function show() { - detailsPanel.p.show(); + detailsPanel.el.show(); } function hide() { - detailsPanel.p.hide(); + detailsPanel.el.hide(); } function destroy() { @@ -216,9 +172,9 @@ angular.module('ovTopo2') .factory('Topo2DeviceDetailsPanel', - ['Topo2PanelService', 'GlyphService', 'WebSocketService', 'FlashService', - 'ButtonService', 'FnService', 'NavService', - function (_ps_, _gs_, _wss_, _flash_, _bs_, _fs_, _ns_) { + ['Topo2DetailsPanelService', 'GlyphService', 'WebSocketService', 'FlashService', + 'ButtonService', 'FnService', 'NavService', 'ListService', + function (_ps_, _gs_, _wss_, _flash_, _bs_, _fs_, _ns_, _listService_) { Panel = _ps_; gs = _gs_; @@ -227,6 +183,7 @@ bs = _bs_; fs = _fs_; ns = _ns_; + listProps = _listService_; return { init: init, diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js index 3fdbeb5315..2fa7bebac9 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js @@ -48,12 +48,34 @@ angular.module('ovTopo2') .factory('Topo2HostService', [ 'Topo2Collection', 'Topo2NodeModel', 'Topo2ViewService', - 'IconService', 'Topo2ZoomService', - function (_Collection_, _NodeModel_, _t2vs_, is, zs) { + 'IconService', 'Topo2ZoomService', 'Topo2HostsPanelService', + function (_Collection_, _NodeModel_, _t2vs_, is, zs, t2hds) { Collection = _Collection_; Model = _NodeModel_.extend({ + initialize: function () { + this.super = this.constructor.__super__; + this.super.initialize.apply(this, arguments); + }, + events: { + 'click': 'onClick' + }, + onChange: function () { + // Update class names when the model changes + if (this.el) { + this.el.attr('class', this.svgClassName()); + } + }, + onClick: function () { + var selected = this.select(d3.select); + + if (selected.length > 0) { + t2hds.displayPanel(this); + } else { + t2hds.hide(); + } + }, nodeType: 'host', icon: function () { var type = this.get('type'); @@ -117,6 +139,7 @@ .attr('text-anchor', 'middle'); this.setScale(); + this.setUpEvents(); } }); diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2HostsPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2HostsPanel.js new file mode 100644 index 0000000000..b131729cd8 --- /dev/null +++ b/web/gui/src/main/webapp/app/view/topo2/topo2HostsPanel.js @@ -0,0 +1,114 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * 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. + */ + +/* + ONOS GUI -- Topology Layout Module. + Module that contains the d3.force.layout logic + */ + +(function () { + 'use strict'; + + // Injected Services + var Panel, gs, wss, flash, listProps; + + // Internal State + var hostPanel, hostData; + + function init() { + hostPanel = Panel(); + } + + function formatHostData(data) { + return { + title: data.get('id'), + propOrder: ['MAC', 'IP', 'VLAN', '-', 'Latitude', 'Longitude'], + props: { + '-': '', + 'MAC': data.get('id'), + 'IP': data.get('ips')[0], + 'VLAN': 'None', // TODO + 'Latitude': data.get('location').lat, + 'Longitude': data.get('location').lng, + } + } + }; + + function displayPanel(data) { + init(); + + hostData = formatHostData(data); + render(); + } + + function render() { + hostPanel.el.show(); + hostPanel.emptyRegions(); + + var svg = hostPanel.appendToHeader('div') + .classed('icon', true) + .append('svg'), + title = hostPanel.appendToHeader('h2'), + table = hostPanel.appendToBody('table'), + tbody = table.append('tbody'); + + title.text(hostData.title); + gs.addGlyph(svg, 'bird', 24, 0, [1, 1]); + listProps(tbody, hostData); + } + + function show() { + hostPanel.el.show(); + } + + function hide() { + hostPanel.el.hide(); + } + + function toggle() { + var on = hostPanel.el.toggle(), + verb = on ? 'Show' : 'Hide'; + flash.flash(verb + ' host Panel'); + } + + function destroy() { + hostPanel.destroy(); + } + + angular.module('ovTopo2') + .factory('Topo2HostsPanelService', + ['Topo2DetailsPanelService', 'GlyphService', 'WebSocketService', 'FlashService', 'ListService', + function (_ps_, _gs_, _wss_, _flash_, _listService_) { + + Panel = _ps_; + gs = _gs_; + wss = _wss_; + flash = _flash_; + listProps = _listService_; + + return { + displayPanel: displayPanel, + init: init, + show: show, + hide: hide, + toggle: toggle, + destroy: destroy, + isVisible: function () { return hostPanel.isVisible(); } + }; + } + ]); + +})(); 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 419f688831..b552a9e2c5 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,7 @@ (function () { 'use strict'; - var $log, Collection, Model, ts, sus, t2zs, t2vs; + var $log, Collection, Model, ts, sus, t2zs, t2vs, t2lps, fn; var linkLabelOffset = '0.35em'; @@ -124,6 +124,16 @@ type: function () { return this.get('type'); }, + svgClassName: function () { + return fn.classNames('link', + this.nodeType, + this.get('type'), + { + enhanced: this.get('enhanced'), + selected: this.get('selected') + } + ); + }, expected: function () { // TODO: original code is: (s && s.expected) && (t && t.expected); return true; @@ -137,6 +147,12 @@ return (sourceOnline) && (targetOnline); }, + onChange: function () { + // Update class names when the model changes + if (this.el) { + this.el.attr('class', this.svgClassName()); + } + }, enhance: function () { var data = [], point; @@ -145,7 +161,7 @@ link.unenhance(); }); - this.el.classed('enhanced', true); + this.set('enhanced', true); if (showPort()) { point = this.locatePortLabel(); @@ -193,9 +209,36 @@ } }, unenhance: function () { - this.el.classed('enhanced', false); + this.set('enhanced', false); d3.select('#topo-portLabels').selectAll('.portLabel').remove(); }, + select: function () { + var ev = d3.event; + + // TODO: if single selection clear selected devices, hosts, sub-regions + var s = Boolean(this.get('selected')); + // Clear all selected Items + _.each(this.collection.models, function (m) { + m.set('selected', false); + }); + + this.set('selected', !s); + + var selected = this.collection.filter(function (m) { + return m.get('selected'); + }); + + return selected; + }, + showDetails: function () { + var selected = this.select(d3.event); + + if (selected) { + t2lps.displayLink(this); + } else { + t2lps.hide(); + } + }, locatePortLabel: function (src) { var offset = 32 / (labelDim * t2zs.scale()), @@ -259,6 +302,7 @@ // from mouse position. this.el.on('mouseover', this.enhance.bind(this)); this.el.on('mouseout', this.unenhance.bind(this)); + this.el.on('click', this.showDetails.bind(this)); if (this.get('type') === 'hostLink') { // sus.visible(link, api.showHosts()); @@ -277,7 +321,7 @@ }, update: function () { - if (this.el.classed('enhanced')) { + if (this.get('enhanced')) { this.enhance(); } } @@ -298,9 +342,9 @@ .factory('Topo2LinkService', ['$log', 'Topo2Collection', 'Topo2Model', 'ThemeService', 'SvgUtilService', 'Topo2ZoomService', - 'Topo2ViewService', + 'Topo2ViewService', 'Topo2LinkPanelService', 'FnService', function (_$log_, _Collection_, _Model_, _ts_, _sus_, - _t2zs_, _t2vs_) { + _t2zs_, _t2vs_, _t2lps_, _fn_) { $log = _$log_; ts = _ts_; @@ -309,6 +353,8 @@ t2vs = _t2vs_; Collection = _Collection_; Model = _Model_; + t2lps = _t2lps_; + fn = _fn_; return { createLinkCollection: createLinkCollection diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2LinkPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2LinkPanel.js new file mode 100644 index 0000000000..cb65ced7a4 --- /dev/null +++ b/web/gui/src/main/webapp/app/view/topo2/topo2LinkPanel.js @@ -0,0 +1,127 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * 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. + */ + +/* + ONOS GUI -- Topology Layout Module. + Module that contains the d3.force.layout logic + */ + +(function () { + 'use strict'; + + // Injected Services + var Panel, gs, wss, flash, listProps; + + // Internal State + var linkPanel, linkData; + + function init() { + linkPanel = Panel(); + } + + function formatLinkData(data) { + + var source = data.get('source'), + target = data.get('target'); + + return { + title: 'Link', + propOrder: [ + 'Type', '-', + 'A Type', 'A Id', 'A Label', 'A Port', '-', + 'B Type', 'B Id', 'B Label', 'B Port' + ], + props: { + '-': '', + 'Type': data.get('type'), + 'A Type': source.get('nodeType'), + 'A Id': source.get('id'), + 'A Label': 'Label', + 'A Port': data.get('portA') || '', + 'B Type': target.get('nodeType'), + 'B Id': target.get('id'), + 'B Label': 'Label', + 'B Port': data.get('portB') || '', + } + } + }; + + function displayLink(data) { + init(); + + linkData = formatLinkData(data); + render(); + } + + function render() { + linkPanel.el.show(); + linkPanel.emptyRegions(); + + var svg = linkPanel.appendToHeader('div') + .classed('icon', true) + .append('svg'), + title = linkPanel.appendToHeader('h2'), + table = linkPanel.appendToBody('table'), + tbody = table.append('tbody'); + + title.text(linkData.title); + gs.addGlyph(svg, 'bird', 24, 0, [1, 1]); + listProps(tbody, linkData); + } + + function show() { + linkPanel.el.show(); + } + + function hide() { + linkPanel.el.hide(); + } + + function toggle() { + var on = linkPanel.el.toggle(), + verb = on ? 'Show' : 'Hide'; + flash.flash(verb + ' Link Panel'); + } + + function destroy() { + wss.unbindHandlers(handlerMap); + linkPanel.destroy(); + } + + angular.module('ovTopo2') + .factory('Topo2LinkPanelService', + ['Topo2DetailsPanelService', 'GlyphService', 'WebSocketService', 'FlashService', 'ListService', + function (_ps_, _gs_, _wss_, _flash_, _listService_) { + + Panel = _ps_; + gs = _gs_; + wss = _wss_; + flash = _flash_; + listProps = _listService_; + + return { + displayLink: displayLink, + init: init, + show: show, + hide: hide, + toggle: toggle, + destroy: destroy, + isVisible: function () { return linkPanel.isVisible(); } + }; + } + ]); + +})(); 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 b1b2a12fd6..f76456e307 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Model.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Model.js @@ -22,6 +22,8 @@ Visualization of the topology in an SVG layer, using a D3 Force Layout. (function () { 'use strict'; + var extend; + function Model(attributes) { var attrs = attributes || {}; @@ -118,34 +120,13 @@ Visualization of the topology in an SVG layer, using a D3 Force Layout. } }; - Model.extend = function (protoProps, staticProps) { - - var parent = this; - var child; - - child = function () { - return parent.apply(this, arguments); - }; - - angular.extend(child, parent, staticProps); - - // Set the prototype chain to inherit from `parent`, without calling - // `parent`'s constructor function and add the prototype properties. - child.prototype = angular.extend({}, parent.prototype, protoProps); - child.prototype.constructor = child; - - // Set a convenience property in case the parent's prototype is needed - // later. - child.__super__ = parent.prototype; - - return child; - }; - angular.module('ovTopo2') .factory('Topo2Model', [ - function () { + 'FnService', + function (fn) { + Model.extend = fn.extend; + return Model; } ]); - })(); diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js b/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js index 048e2f9e5c..18ad7cc197 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js @@ -76,6 +76,31 @@ 'mouseout': 'mouseoutHandler' }; }, + select: function () { + var ev = d3.event; + + // TODO: if single selection clear selected devices, hosts, sub-regions + + if (ev.shiftKey) { + // TODO: Multi-Select Details Panel + this.set('selected', true); + } else { + + var s = Boolean(this.get('selected')); + // Clear all selected Items + _.each(this.collection.models, function (m) { + m.set('selected', false); + }); + + this.set('selected', !s); + } + + var selected = this.collection.filter(function (m) { + return m.get('selected'); + }); + + return selected; + }, createNode: function () { this.set('svgClass', this.svgClassName()); t2nps.positionNode(this); diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Panel.js b/web/gui/src/main/webapp/app/view/topo2/topo2Panel.js index 3687f6b5ba..686ec96c0d 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Panel.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Panel.js @@ -22,30 +22,24 @@ (function () { 'use strict'; - var ps; + // Injected Services + var flash, ps; - var Panel = function (id, options) { - this.id = id; - this.p = ps.createPanel(this.id, options); - this.setup(); + var panel = { + initialize: function (id, options) { + this.id = id; + this.el = ps.createPanel(id, options); + this.setup(); - if (options.show) { - this.p.show(); - } - }; - - Panel.prototype = { + if (options.show) { + this.el.show(); + } + }, setup: function () { - var panel = this.p; - panel.empty(); - - panel.append('div').classed('header', true); - panel.append('div').classed('body', true); - panel.append('div').classed('footer', true); - - this.header = panel.el().select('.header'); - this.body = panel.el().select('.body'); - this.footer = panel.el().select('.body'); + this.el.empty(); + this.header = this.el.append('div').classed('header', true); + this.body = this.el.append('div').classed('body', true); + this.footer = this.el.append('div').classed('footer', true); }, appendToHeader: function (x) { return this.header.append(x); @@ -65,15 +59,19 @@ ps.destroyPanel(this.id); }, isVisible: function () { - return this.p.isVisible(); + return this.el.isVisible(); } - }; + } angular.module('ovTopo2') - .factory('Topo2PanelService', ['PanelService', - function (_ps_) { + .factory('Topo2PanelService', + ['Topo2UIView', 'FlashService', 'PanelService', + function (View, _flash_, _ps_) { + + flash = _flash_; ps = _ps_; - return Panel; + + return View.extend(panel); } ]); diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Region.js b/web/gui/src/main/webapp/app/view/topo2/topo2Region.js index bd24bbb00e..24df147093 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2Region.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2Region.js @@ -54,8 +54,6 @@ link.createLink(); }); - console.log(region.get('id')); - // TEMP Map Zoom var regionPanZooms = { "(root)": { diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js index bb64578c26..a370746929 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js @@ -41,9 +41,9 @@ angular.module('ovTopo2') .factory('Topo2SubRegionService', ['WebSocketService', 'Topo2Collection', 'Topo2NodeModel', - 'ThemeService', 'Topo2ViewService', + 'ThemeService', 'Topo2ViewService', 'Topo2SubRegionPanelService', - function (_wss_, _c_, _NodeModel_, _ts_, _t2vs_) { + function (_wss_, _c_, _NodeModel_, _ts_, _t2vs_m, _t2srp_) { wss = _wss_; Collection = _c_; @@ -54,13 +54,29 @@ this.super.initialize.apply(this, arguments); }, events: { - 'dblclick': 'navigateToRegion' + 'dblclick': 'navigateToRegion', + 'click': 'onClick' + }, + onChange: function () { + // Update class names when the model changes + if (this.el) { + this.el.attr('class', this.svgClassName()); + } }, nodeType: 'sub-region', icon: function () { var type = this.get('type'); return remappedDeviceTypes[type] || type || 'm_cloud'; }, + onClick: function () { + var selected = this.select(d3.event); + + if (selected.length > 0) { + _t2srp_.displayPanel(this); + } else { + _t2srp_.hide(); + } + }, navigateToRegion: function () { if (d3.event.defaultPrevented) return; diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegionPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegionPanel.js new file mode 100644 index 0000000000..b5e49b78cd --- /dev/null +++ b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegionPanel.js @@ -0,0 +1,112 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * 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. + */ + +/* + ONOS GUI -- Topology Layout Module. + Module that contains the d3.force.layout logic + */ + +(function () { + 'use strict'; + + // Injected Services + var Panel, gs, wss, flash, listProps; + + // Internal State + var subRegionPanel, subRegionData; + + function init() { + subRegionPanel = Panel(); + } + + function formatSubRegionData(data) { + return { + title: data.get('name'), + propOrder: ['Id', 'Type', '-', 'Number of Devices', 'Number of Hosts'], + props: { + '-': '', + 'Id': data.get('id'), + 'Type': data.get('nodeType'), + 'Number of Devices': data.get('nDevs'), + 'Number of Hosts': data.get('nHosts') + } + } + }; + + function displayPanel(data) { + init(); + subRegionData = formatSubRegionData(data); + render(); + } + + function render() { + subRegionPanel.el.show(); + subRegionPanel.emptyRegions(); + + var svg = subRegionPanel.appendToHeader('div') + .classed('icon', true) + .append('svg'), + title = subRegionPanel.appendToHeader('h2'), + table = subRegionPanel.appendToBody('table'), + tbody = table.append('tbody'); + + title.text(subRegionData.title); + gs.addGlyph(svg, 'bird', 24, 0, [1, 1]); + listProps(tbody, subRegionData); + } + + function show() { + subRegionPanel.el.show(); + } + + function hide() { + subRegionPanel.el.hide(); + } + + function toggle() { + var on = subRegionPanel.el.toggle(), + verb = on ? 'Show' : 'Hide'; + flash.flash(verb + ' subRegion Panel'); + } + + function destroy() { + subRegionPanel.destroy(); + } + + angular.module('ovTopo2') + .factory('Topo2SubRegionPanelService', + ['Topo2DetailsPanelService', 'GlyphService', 'WebSocketService', 'FlashService', 'ListService', + function (_ps_, _gs_, _wss_, _flash_, _listService_) { + + Panel = _ps_; + gs = _gs_; + wss = _wss_; + flash = _flash_; + listProps = _listService_; + + return { + displayPanel: displayPanel, + init: init, + show: show, + hide: hide, + toggle: toggle, + destroy: destroy, + isVisible: function () { return subRegionPanel.isVisible(); } + }; + } + ]); + +})(); diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2SummaryPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2SummaryPanel.js index 43e92fbdd2..b7e671cad0 100644 --- a/web/gui/src/main/webapp/app/view/topo2/topo2SummaryPanel.js +++ b/web/gui/src/main/webapp/app/view/topo2/topo2SummaryPanel.js @@ -23,7 +23,7 @@ 'use strict'; // Injected Services - var Panel, gs, wss, flash; + var Panel, gs, wss, flash, listProps; // Internal State var summaryPanel, summaryData; @@ -33,7 +33,7 @@ className = 'topo-p', panelOpts = { show: true, - width: 260 // summary and detail panel width + width: 260 // summary and detail panel width }, handlerMap = { showSummary: handleSummaryData @@ -49,37 +49,7 @@ }); summaryPanel = new Panel(id, options); - summaryPanel.p.classed(className, true); - } - - function addProp(tbody, label, value) { - var tr = tbody.append('tr'), - lab; - if (typeof label === 'string') { - lab = label.replace(/_/g, ' '); - } else { - lab = label; - } - - function addCell(cls, txt) { - tr.append('td').attr('class', cls).html(txt); - } - addCell('label', lab + ' :'); - addCell('value', value); - } - - function addSep(tbody) { - tbody.append('tr').append('td').attr('colspan', 2).append('hr'); - } - - function listProps(tbody, data) { - summaryData.propOrder.forEach(function (p) { - if (p === '-') { - addSep(tbody); - } else { - addProp(tbody, p, summaryData.props[p]); - } - }); + summaryPanel.el.classed(className, true); } function render() { @@ -94,7 +64,7 @@ title.text(summaryData.title); gs.addGlyph(svg, 'bird', 24, 0, [1, 1]); - listProps(tbody); + listProps(tbody, summaryData); } function handleSummaryData(data) { @@ -107,7 +77,7 @@ } function toggle() { - var on = summaryPanel.p.toggle(), + var on = summaryPanel.el.toggle(), verb = on ? 'Show' : 'Hide'; flash.flash(verb + ' Summary Panel'); } @@ -119,17 +89,17 @@ angular.module('ovTopo2') .factory('Topo2SummaryPanelService', - ['Topo2PanelService', 'GlyphService', 'WebSocketService', 'FlashService', - function (_ps_, _gs_, _wss_, _flash_) { + ['Topo2PanelService', 'GlyphService', 'WebSocketService', 'FlashService', 'ListService', + function (_ps_, _gs_, _wss_, _flash_, _listService_) { Panel = _ps_; gs = _gs_; wss = _wss_; flash = _flash_; + listProps = _listService_; return { init: init, - toggle: toggle, destroy: destroy, isVisible: function () { return summaryPanel.isVisible(); } diff --git a/web/gui/src/main/webapp/app/view/topo2/uiView.js b/web/gui/src/main/webapp/app/view/topo2/uiView.js new file mode 100644 index 0000000000..8bc01cca46 --- /dev/null +++ b/web/gui/src/main/webapp/app/view/topo2/uiView.js @@ -0,0 +1,58 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * 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. + */ + +/* + ONOS GUI -- Base UIView class. + A base class for UIViews to extend from + */ + +(function () { + 'use strict'; + + function View(options) { + if (options && options.el) { + this.el = options.el; + this.$el = angular.element(this.el); + } + + this.initialize.apply(this, arguments); + } + + angular.module('ovTopo2') + .factory('Topo2UIView', + ['FnService', + function (fn) { + + _.extend(View.prototype, { + el: null, + empty: function () { + if (this.$el) { + this.$el.empty(); + } + }, + destroy: function () { + // TODO: Unbind Events + this.empty(); + return this; + } + }); + + View.extend = fn.extend; + return View; + } + ]); + +})(); diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html index 0f914496f3..9f321f22a2 100644 --- a/web/gui/src/main/webapp/index.html +++ b/web/gui/src/main/webapp/index.html @@ -83,6 +83,7 @@ + @@ -127,20 +128,23 @@ - diff --git a/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js b/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js index e535460bb9..bc434cac90 100644 --- a/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js +++ b/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js @@ -212,12 +212,12 @@ describe('factory: fw/util/fn.js', function() { it('should define api functions', function () { expect(fs.areFunctions(fs, [ 'isF', 'isA', 'isS', 'isO', 'contains', - 'areFunctions', 'areFunctionsNonStrict', 'windowSize', + 'areFunctions', 'areFunctionsNonStrict', 'windowSize', 'isMobile', 'isChrome', 'isSafari', 'isFirefox', 'debugOn', 'debug', 'find', 'inArray', 'removeFromArray', 'isEmptyObject', 'sameObjProps', 'containsObj', 'cap', 'eecode', 'noPx', 'noPxStyle', 'endsWith', 'parseBitRate', 'addToTrie', 'removeFromTrie', 'trieLookup', - 'classNames' + 'classNames', 'extend' ])).toBeTruthy(); });