Committed by
Simon Hunt
Added Collections and Models for a Region.
Change-Id: Ic033b2890dad18e47b057e6b1d1c8535d812590d
Showing
12 changed files
with
1007 additions
and
31 deletions
... | @@ -22,5 +22,7 @@ bucklets/plugins/ | ... | @@ -22,5 +22,7 @@ bucklets/plugins/ |
22 | /bin/ | 22 | /bin/ |
23 | 23 | ||
24 | web/gui/src/main/webapp/tests/node_modules | 24 | web/gui/src/main/webapp/tests/node_modules |
25 | +web/gui/src/test/_karma/node_modules | ||
25 | web/gui/src/main/webapp/node_modules/ | 26 | web/gui/src/main/webapp/node_modules/ |
27 | + | ||
26 | npm-debug.log | 28 | npm-debug.log | ... | ... |
1 | /* | 1 | /* |
2 | - * Copyright 2016-present Open Networking Laboratory | 2 | +* Copyright 2016-present Open Networking Laboratory |
3 | - * | 3 | +* |
4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | +* Licensed under the Apache License, Version 2.0 (the "License"); |
5 | - * you may not use this file except in compliance with the License. | 5 | +* you may not use this file except in compliance with the License. |
6 | - * You may obtain a copy of the License at | 6 | +* You may obtain a copy of the License at |
7 | - * | 7 | +* |
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | 8 | +* http://www.apache.org/licenses/LICENSE-2.0 |
9 | - * | 9 | +* |
10 | - * Unless required by applicable law or agreed to in writing, software | 10 | +* Unless required by applicable law or agreed to in writing, software |
11 | - * distributed under the License is distributed on an "AS IS" BASIS, | 11 | +* distributed under the License is distributed on an "AS IS" BASIS, |
12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 12 | +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | - * See the License for the specific language governing permissions and | 13 | +* See the License for the specific language governing permissions and |
14 | - * limitations under the License. | 14 | +* limitations under the License. |
15 | - */ | 15 | +*/ |
16 | 16 | ||
17 | /* | 17 | /* |
18 | ONOS GUI -- Topology View Module | 18 | ONOS GUI -- Topology View Module |
... | @@ -28,7 +28,7 @@ | ... | @@ -28,7 +28,7 @@ |
28 | fs, mast, ks, zs, | 28 | fs, mast, ks, zs, |
29 | gs, ms, sus, flash, | 29 | gs, ms, sus, flash, |
30 | wss, ps, th, | 30 | wss, ps, th, |
31 | - t2es, t2fs; | 31 | + t2es, t2fs, t2is; |
32 | 32 | ||
33 | // DOM elements | 33 | // DOM elements |
34 | var ovtopo2, svg, defs, zoomLayer, mapG, spriteG, forceG, noDevsLayer; | 34 | var ovtopo2, svg, defs, zoomLayer, mapG, spriteG, forceG, noDevsLayer; |
... | @@ -37,17 +37,51 @@ | ... | @@ -37,17 +37,51 @@ |
37 | var zoomer, actionMap; | 37 | var zoomer, actionMap; |
38 | 38 | ||
39 | 39 | ||
40 | - // === Helper Functions | 40 | + // --- Glyphs, Icons, and the like ----------------------------------- |
41 | + | ||
42 | + function setUpDefs() { | ||
43 | + defs = svg.append('defs'); | ||
44 | + gs.loadDefs(defs); | ||
45 | + sus.loadGlowDefs(defs); | ||
46 | + } | ||
41 | 47 | ||
42 | // callback invoked when the SVG view has been resized.. | 48 | // callback invoked when the SVG view has been resized.. |
43 | function svgResized(s) { | 49 | function svgResized(s) { |
44 | - $log.debug("topo2 view resized", s); | 50 | + $log.debug('topo2 view resized', s); |
45 | } | 51 | } |
46 | 52 | ||
47 | function setUpKeys(overlayKeys) { | 53 | function setUpKeys(overlayKeys) { |
48 | $log.debug('topo2: set up keys....'); | 54 | $log.debug('topo2: set up keys....'); |
49 | } | 55 | } |
50 | 56 | ||
57 | + // --- Pan and Zoom -------------------------------------------------- | ||
58 | + | ||
59 | + // zoom enabled predicate. ev is a D3 source event. | ||
60 | + function zoomEnabled(ev) { | ||
61 | + return fs.isMobile() || (ev.metaKey || ev.altKey); | ||
62 | + } | ||
63 | + | ||
64 | + function zoomCallback() { | ||
65 | + var sc = zoomer.scale(), | ||
66 | + tr = zoomer.translate(); | ||
67 | + | ||
68 | + ps.setPrefs('topo_zoom', {tx:tr[0], ty:tr[1], sc:sc}); | ||
69 | + | ||
70 | + // keep the map lines constant width while zooming | ||
71 | + mapG.style('stroke-width', (2.0 / sc) + 'px'); | ||
72 | + } | ||
73 | + | ||
74 | + function setUpZoom() { | ||
75 | + zoomLayer = svg.append('g').attr('id', 'topo-zoomlayer'); | ||
76 | + zoomer = zs.createZoomer({ | ||
77 | + svg: svg, | ||
78 | + zoomLayer: zoomLayer, | ||
79 | + zoomEnabled: zoomEnabled, | ||
80 | + zoomCallback: zoomCallback | ||
81 | + }); | ||
82 | + } | ||
83 | + | ||
84 | + | ||
51 | // === Controller Definition ----------------------------------------- | 85 | // === Controller Definition ----------------------------------------- |
52 | 86 | ||
53 | angular.module('ovTopo2', ['onosUtil', 'onosSvg', 'onosRemote']) | 87 | angular.module('ovTopo2', ['onosUtil', 'onosSvg', 'onosRemote']) |
... | @@ -56,13 +90,13 @@ | ... | @@ -56,13 +90,13 @@ |
56 | 'FnService', 'MastService', 'KeyService', 'ZoomService', | 90 | 'FnService', 'MastService', 'KeyService', 'ZoomService', |
57 | 'GlyphService', 'MapService', 'SvgUtilService', 'FlashService', | 91 | 'GlyphService', 'MapService', 'SvgUtilService', 'FlashService', |
58 | 'WebSocketService', 'PrefsService', 'ThemeService', | 92 | 'WebSocketService', 'PrefsService', 'ThemeService', |
59 | - 'Topo2EventService', 'Topo2ForceService', | 93 | + 'Topo2EventService', 'Topo2ForceService', 'Topo2InstanceService', |
60 | 94 | ||
61 | function (_$scope_, _$log_, _$loc_, | 95 | function (_$scope_, _$log_, _$loc_, |
62 | _fs_, _mast_, _ks_, _zs_, | 96 | _fs_, _mast_, _ks_, _zs_, |
63 | _gs_, _ms_, _sus_, _flash_, | 97 | _gs_, _ms_, _sus_, _flash_, |
64 | _wss_, _ps_, _th_, | 98 | _wss_, _ps_, _th_, |
65 | - _t2es_, _t2fs_) { | 99 | + _t2es_, _t2fs_, _t2is_) { |
66 | 100 | ||
67 | var params = _$loc_.search(), | 101 | var params = _$loc_.search(), |
68 | projection, | 102 | projection, |
... | @@ -98,6 +132,7 @@ | ... | @@ -98,6 +132,7 @@ |
98 | 132 | ||
99 | t2es = _t2es_; | 133 | t2es = _t2es_; |
100 | t2fs = _t2fs_; | 134 | t2fs = _t2fs_; |
135 | + t2is = _t2is_; | ||
101 | 136 | ||
102 | // capture selected intent parameters (if they are set in the | 137 | // capture selected intent parameters (if they are set in the |
103 | // query string) so that the traffic overlay can highlight | 138 | // query string) so that the traffic overlay can highlight |
... | @@ -134,12 +169,15 @@ | ... | @@ -134,12 +169,15 @@ |
134 | 169 | ||
135 | // set up our keyboard shortcut bindings | 170 | // set up our keyboard shortcut bindings |
136 | setUpKeys(); | 171 | setUpKeys(); |
172 | + setUpZoom(); | ||
173 | + setUpDefs(); | ||
137 | 174 | ||
138 | // make sure we can respond to topology events from the server | 175 | // make sure we can respond to topology events from the server |
139 | t2es.bindHandlers(); | 176 | t2es.bindHandlers(); |
140 | 177 | ||
141 | // initialize the force layout, ready to render the topology | 178 | // initialize the force layout, ready to render the topology |
142 | - t2fs.init(); | 179 | + forceG = zoomLayer.append('g').attr('id', 'topo-force'); |
180 | + t2fs.init(svg, forceG, uplink, dim); | ||
143 | 181 | ||
144 | 182 | ||
145 | // =-=-=-=-=-=-=-=- | 183 | // =-=-=-=-=-=-=-=- |
... | @@ -150,6 +188,11 @@ | ... | @@ -150,6 +188,11 @@ |
150 | t2es.start(); | 188 | t2es.start(); |
151 | 189 | ||
152 | 190 | ||
191 | + | ||
192 | + t2is.initInst({ showMastership: t2fs.showMastership }); | ||
193 | + | ||
194 | + | ||
195 | + | ||
153 | // === ORIGINAL CODE === | 196 | // === ORIGINAL CODE === |
154 | 197 | ||
155 | // setUpKeys(); | 198 | // setUpKeys(); | ... | ... |
1 | +/* | ||
2 | + * Copyright 2016-present Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- Topology Collection Module. | ||
19 | + A Data Store that contains model data from the server | ||
20 | + */ | ||
21 | + | ||
22 | +(function () { | ||
23 | + 'use strict'; | ||
24 | + | ||
25 | + var Model; | ||
26 | + | ||
27 | + function Collection(models, options) { | ||
28 | + | ||
29 | + options || (options = {}); | ||
30 | + | ||
31 | + this.models = []; | ||
32 | + this._reset(); | ||
33 | + | ||
34 | + if (options.comparator !== void 0) this.comparator = options.comparator; | ||
35 | + | ||
36 | + if (models) { | ||
37 | + this.add(models); | ||
38 | + } | ||
39 | + } | ||
40 | + | ||
41 | + Collection.prototype = { | ||
42 | + model: Model, | ||
43 | + add: function (data) { | ||
44 | + | ||
45 | + var _this = this; | ||
46 | + | ||
47 | + if (angular.isArray(data)) { | ||
48 | + | ||
49 | + data.forEach(function (d) { | ||
50 | + | ||
51 | + var model = new _this.model(d); | ||
52 | + model.collection = _this; | ||
53 | + | ||
54 | + _this.models.push(model); | ||
55 | + _this._byId[d.id] = model; | ||
56 | + }); | ||
57 | + } | ||
58 | + | ||
59 | +// this.sort(); | ||
60 | + }, | ||
61 | + get: function (id) { | ||
62 | + if (!id) { | ||
63 | + return void 0; | ||
64 | + } | ||
65 | + return this._byId[id] || null; | ||
66 | + }, | ||
67 | + sort: function () { | ||
68 | + | ||
69 | + var comparator = this.comparator; | ||
70 | + | ||
71 | + // Check if function | ||
72 | + comparator = comparator.bind(this); | ||
73 | + this.models.sort(comparator); | ||
74 | + | ||
75 | + return this; | ||
76 | + }, | ||
77 | + _reset: function () { | ||
78 | + this._byId = []; | ||
79 | + this.models = []; | ||
80 | + } | ||
81 | + }; | ||
82 | + | ||
83 | + Collection.extend = function (protoProps, staticProps) { | ||
84 | + | ||
85 | + var parent = this; | ||
86 | + var child; | ||
87 | + | ||
88 | + child = function () { | ||
89 | + return parent.apply(this, arguments); | ||
90 | + }; | ||
91 | + | ||
92 | + angular.extend(child, parent, staticProps); | ||
93 | + | ||
94 | + // Set the prototype chain to inherit from `parent`, without calling | ||
95 | + // `parent`'s constructor function and add the prototype properties. | ||
96 | + child.prototype = angular.extend({}, parent.prototype, protoProps); | ||
97 | + child.prototype.constructor = child; | ||
98 | + | ||
99 | + // Set a convenience property in case the parent's prototype is needed | ||
100 | + // later. | ||
101 | + child.__super__ = parent.prototype; | ||
102 | + | ||
103 | + return child; | ||
104 | + }; | ||
105 | + | ||
106 | + angular.module('ovTopo2') | ||
107 | + .factory('Topo2Collection', | ||
108 | + ['Topo2Model', | ||
109 | + function (_Model_) { | ||
110 | + | ||
111 | + Model = _Model_; | ||
112 | + return Collection; | ||
113 | + } | ||
114 | + ]); | ||
115 | + | ||
116 | +})(); |
1 | +/* | ||
2 | + * Copyright 2016-present Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- Topology Devices Module. | ||
19 | + Module that holds the devices for a region | ||
20 | + */ | ||
21 | + | ||
22 | +(function () { | ||
23 | + 'use strict'; | ||
24 | + | ||
25 | + var Collection, Model; | ||
26 | + | ||
27 | + function createDeviceCollection(data, region) { | ||
28 | + | ||
29 | + var DeviceCollection = Collection.extend({ | ||
30 | + model: Model, | ||
31 | + get: function () {}, | ||
32 | + comparator: function(a, b) { | ||
33 | + | ||
34 | + var order = region.layerOrder; | ||
35 | + return order.indexOf(a.get('layer')) - order.indexOf(b.get('layer')); | ||
36 | + } | ||
37 | + }); | ||
38 | + | ||
39 | + var devices = []; | ||
40 | + data.forEach(function (deviceLayer) { | ||
41 | + deviceLayer.forEach(function (device) { | ||
42 | + devices.push(device); | ||
43 | + }); | ||
44 | + }); | ||
45 | + | ||
46 | + var deviceCollection = new DeviceCollection(devices); | ||
47 | + deviceCollection.sort(); | ||
48 | + | ||
49 | + return deviceCollection; | ||
50 | + } | ||
51 | + | ||
52 | + angular.module('ovTopo2') | ||
53 | + .factory('Topo2DeviceService', | ||
54 | + ['Topo2Collection', 'Topo2Model', | ||
55 | + | ||
56 | + function (_Collection_, _Model_) { | ||
57 | + | ||
58 | + Collection = _Collection_; | ||
59 | + Model = _Model_.extend({}); | ||
60 | + | ||
61 | + return { | ||
62 | + createDeviceCollection: createDeviceCollection | ||
63 | + }; | ||
64 | + } | ||
65 | + ]); | ||
66 | + | ||
67 | +})(); |
... | @@ -23,12 +23,99 @@ | ... | @@ -23,12 +23,99 @@ |
23 | 'use strict'; | 23 | 'use strict'; |
24 | 24 | ||
25 | // injected refs | 25 | // injected refs |
26 | - var $log, wss; | 26 | + var $log, |
27 | + wss; | ||
28 | + | ||
29 | + // SVG elements; | ||
30 | + var linkG, | ||
31 | + linkLabelG, | ||
32 | + numLinkLblsG, | ||
33 | + portLabelG, | ||
34 | + nodeG; | ||
35 | + | ||
36 | + // internal state | ||
37 | + var settings, // merged default settings and options | ||
38 | + force, // force layout object | ||
39 | + drag, // drag behavior handler | ||
40 | + network = { | ||
41 | + nodes: [], | ||
42 | + links: [], | ||
43 | + linksByDevice: {}, | ||
44 | + lookup: {}, | ||
45 | + revLinkToKey: {} | ||
46 | + }, | ||
47 | + lu, // shorthand for lookup | ||
48 | + rlk, // shorthand for revLinktoKey | ||
49 | + showHosts = false, // whether hosts are displayed | ||
50 | + showOffline = true, // whether offline devices are displayed | ||
51 | + nodeLock = false, // whether nodes can be dragged or not (locked) | ||
52 | + fTimer, // timer for delayed force layout | ||
53 | + fNodesTimer, // timer for delayed nodes update | ||
54 | + fLinksTimer, // timer for delayed links update | ||
55 | + dim, // the dimensions of the force layout [w,h] | ||
56 | + linkNums = []; // array of link number labels | ||
57 | + | ||
58 | + // D3 selections; | ||
59 | + var link, | ||
60 | + linkLabel, | ||
61 | + node; | ||
62 | + | ||
63 | + var $log, wss, t2is, t2rs; | ||
27 | 64 | ||
28 | // ========================== Helper Functions | 65 | // ========================== Helper Functions |
29 | 66 | ||
30 | - function init() { | 67 | + function init(_svg_, forceG, _uplink_, _dim_, opts) { |
68 | + | ||
31 | $log.debug('Initialize topo force layout'); | 69 | $log.debug('Initialize topo force layout'); |
70 | + | ||
71 | + nodeG = forceG.append('g').attr('id', 'topo-nodes'); | ||
72 | + node = nodeG.selectAll('.node'); | ||
73 | + | ||
74 | + linkG = forceG.append('g').attr('id', 'topo-links'); | ||
75 | + linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels'); | ||
76 | + numLinkLblsG = forceG.append('g').attr('id', 'topo-numLinkLabels'); | ||
77 | + nodeG = forceG.append('g').attr('id', 'topo-nodes'); | ||
78 | + portLabelG = forceG.append('g').attr('id', 'topo-portLabels'); | ||
79 | + | ||
80 | + link = linkG.selectAll('.link'); | ||
81 | + linkLabel = linkLabelG.selectAll('.linkLabel'); | ||
82 | + node = nodeG.selectAll('.node'); | ||
83 | + | ||
84 | + var width = 640, | ||
85 | + height = 480; | ||
86 | + | ||
87 | + var nodes = [ | ||
88 | + { x: width/3, y: height/2 }, | ||
89 | + { x: 2*width/3, y: height/2 } | ||
90 | + ]; | ||
91 | + | ||
92 | + var links = [ | ||
93 | + { source: 0, target: 1 } | ||
94 | + ]; | ||
95 | + | ||
96 | + var svg = d3.select('body').append('svg') | ||
97 | + .attr('width', width) | ||
98 | + .attr('height', height); | ||
99 | + | ||
100 | + var force = d3.layout.force() | ||
101 | + .size([width, height]) | ||
102 | + .nodes(nodes) | ||
103 | + .links(links); | ||
104 | + | ||
105 | + force.linkDistance(width/2); | ||
106 | + | ||
107 | + | ||
108 | + var link = svg.selectAll('.link') | ||
109 | + .data(links) | ||
110 | + .enter().append('line') | ||
111 | + .attr('class', 'link'); | ||
112 | + | ||
113 | + var node = svg.selectAll('.node') | ||
114 | + .data(nodes) | ||
115 | + .enter().append('circle') | ||
116 | + .attr('class', 'node'); | ||
117 | + | ||
118 | + force.start(); | ||
32 | } | 119 | } |
33 | 120 | ||
34 | function destroy() { | 121 | function destroy() { |
... | @@ -106,17 +193,19 @@ | ... | @@ -106,17 +193,19 @@ |
106 | // ========================== Event Handlers | 193 | // ========================== Event Handlers |
107 | 194 | ||
108 | function allInstances(data) { | 195 | function allInstances(data) { |
109 | - $log.debug('>> topo2AllInstances event:', data) | 196 | + $log.debug('>> topo2AllInstances event:', data); |
110 | doTmpCurrentLayout(data); | 197 | doTmpCurrentLayout(data); |
198 | + t2is.allInstances(data); | ||
111 | } | 199 | } |
112 | 200 | ||
113 | function currentLayout(data) { | 201 | function currentLayout(data) { |
114 | - $log.debug('>> topo2CurrentLayout event:', data) | 202 | + $log.debug('>> topo2CurrentLayout event:', data); |
115 | } | 203 | } |
116 | 204 | ||
117 | function currentRegion(data) { | 205 | function currentRegion(data) { |
118 | - $log.debug('>> topo2CurrentRegion event:', data) | 206 | + $log.debug('>> topo2CurrentRegion event:', data); |
119 | doTmpCurrentRegion(data); | 207 | doTmpCurrentRegion(data); |
208 | + t2rs.addRegion(data); | ||
120 | } | 209 | } |
121 | 210 | ||
122 | function topo2PeerRegions(data) { | 211 | function topo2PeerRegions(data) { |
... | @@ -129,27 +218,68 @@ | ... | @@ -129,27 +218,68 @@ |
129 | } | 218 | } |
130 | 219 | ||
131 | function startDone(data) { | 220 | function startDone(data) { |
132 | - $log.debug('>> topo2StartDone event:', data) | 221 | + $log.debug('>> topo2StartDone event:', data); |
222 | + } | ||
223 | + | ||
224 | + | ||
225 | + function showMastership(masterId) { | ||
226 | + if (!masterId) { | ||
227 | + restoreLayerState(); | ||
228 | + } else { | ||
229 | + showMastershipFor(masterId); | ||
230 | + } | ||
231 | + } | ||
232 | + | ||
233 | + function restoreLayerState() { | ||
234 | + // NOTE: this level of indirection required, for when we have | ||
235 | + // the layer filter functionality re-implemented | ||
236 | + suppressLayers(false); | ||
237 | + } | ||
238 | + | ||
239 | + // ========================== Main Service Definition | ||
240 | + | ||
241 | + function showMastershipFor(id) { | ||
242 | + suppressLayers(true); | ||
243 | + node.each(function (n) { | ||
244 | + if (n.master === id) { | ||
245 | + n.el.classed('suppressedmax', false); | ||
246 | + } | ||
247 | + }); | ||
248 | + } | ||
249 | + | ||
250 | + function supAmt(less) { | ||
251 | + return less ? 'suppressed' : 'suppressedmax'; | ||
252 | + } | ||
253 | + | ||
254 | + function suppressLayers(b, less) { | ||
255 | + var cls = supAmt(less); | ||
256 | + node.classed(cls, b); | ||
257 | + // link.classed(cls, b); | ||
133 | } | 258 | } |
134 | 259 | ||
135 | // ========================== Main Service Definition | 260 | // ========================== Main Service Definition |
136 | 261 | ||
137 | angular.module('ovTopo2') | 262 | angular.module('ovTopo2') |
138 | .factory('Topo2ForceService', | 263 | .factory('Topo2ForceService', |
139 | - ['$log', 'WebSocketService', | 264 | + ['$log', 'WebSocketService', 'Topo2InstanceService', 'Topo2RegionService', |
140 | - | 265 | + function (_$log_, _wss_, _t2is_, _t2rs_) { |
141 | - function (_$log_, _wss_) { | ||
142 | $log = _$log_; | 266 | $log = _$log_; |
143 | wss = _wss_; | 267 | wss = _wss_; |
268 | + t2is = _t2is_; | ||
269 | + t2rs = _t2rs_; | ||
144 | 270 | ||
145 | return { | 271 | return { |
272 | + | ||
146 | init: init, | 273 | init: init, |
274 | + | ||
147 | destroy: destroy, | 275 | destroy: destroy, |
148 | topo2AllInstances: allInstances, | 276 | topo2AllInstances: allInstances, |
149 | topo2CurrentLayout: currentLayout, | 277 | topo2CurrentLayout: currentLayout, |
150 | topo2CurrentRegion: currentRegion, | 278 | topo2CurrentRegion: currentRegion, |
151 | - topo2PeerRegions: topo2PeerRegions, | 279 | + topo2StartDone: startDone, |
152 | - topo2StartDone: startDone | 280 | + |
281 | + showMastership: showMastership, | ||
282 | + topo2PeerRegions: topo2PeerRegions | ||
153 | }; | 283 | }; |
154 | }]); | 284 | }]); |
155 | }()); | 285 | }()); | ... | ... |
1 | +/* | ||
2 | + * Copyright 2016-present Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- Topology Hosts Module. | ||
19 | + Module that holds the hosts for a region | ||
20 | + */ | ||
21 | + | ||
22 | +(function () { | ||
23 | + 'use strict'; | ||
24 | + | ||
25 | + var Collection, Model; | ||
26 | + | ||
27 | + function createHostCollection(data, region) { | ||
28 | + | ||
29 | + var HostCollection = Collection.extend({ | ||
30 | + model: Model | ||
31 | + }); | ||
32 | + | ||
33 | + var hosts = []; | ||
34 | + data.forEach(function (hostsLayer) { | ||
35 | + hostsLayer.forEach(function (host) { | ||
36 | + hosts.push(host); | ||
37 | + }); | ||
38 | + }); | ||
39 | + | ||
40 | + return new HostCollection(hosts); | ||
41 | + } | ||
42 | + | ||
43 | + angular.module('ovTopo2') | ||
44 | + .factory('Topo2HostService', | ||
45 | + ['Topo2Collection', 'Topo2Model', | ||
46 | + | ||
47 | + function (_Collection_, _Model_) { | ||
48 | + | ||
49 | + Collection = _Collection_; | ||
50 | + Model = _Model_.extend(); | ||
51 | + | ||
52 | + return { | ||
53 | + createHostCollection: createHostCollection | ||
54 | + }; | ||
55 | + } | ||
56 | + ]); | ||
57 | + | ||
58 | +})(); |
1 | +(function () { | ||
2 | + 'use strict'; | ||
3 | + | ||
4 | + // injected refs | ||
5 | + var $log, | ||
6 | + ps, | ||
7 | + sus, | ||
8 | + gs, | ||
9 | + ts, | ||
10 | + fs, | ||
11 | + flash; | ||
12 | + | ||
13 | + // api from topo | ||
14 | + var api; | ||
15 | + | ||
16 | + // configuration | ||
17 | + var showLogicErrors = true, | ||
18 | + idIns = 'topo-p-instance', | ||
19 | + instOpts = { | ||
20 | + edge: 'left', | ||
21 | + width: 20 | ||
22 | + }; | ||
23 | + | ||
24 | + // internal state | ||
25 | + var onosInstances, | ||
26 | + onosOrder, | ||
27 | + oiShowMaster, | ||
28 | + oiBox; | ||
29 | + | ||
30 | + | ||
31 | + function addInstance(data) { | ||
32 | + var id = data.id; | ||
33 | + | ||
34 | + if (onosInstances[id]) { | ||
35 | + updateInstance(data); | ||
36 | + return; | ||
37 | + } | ||
38 | + onosInstances[id] = data; | ||
39 | + onosOrder.push(data); | ||
40 | + updateInstances(); | ||
41 | + } | ||
42 | + | ||
43 | + function updateInstance(data) { | ||
44 | + var id = data.id, | ||
45 | + d = onosInstances[id]; | ||
46 | + if (d) { | ||
47 | + angular.extend(d, data); | ||
48 | + updateInstances(); | ||
49 | + } else { | ||
50 | + logicError('updateInstance: lookup fail: ID = "' + id + '"'); | ||
51 | + } | ||
52 | + } | ||
53 | + | ||
54 | + function removeInstance(data) { | ||
55 | + var id = data.id, | ||
56 | + d = onosInstances[id]; | ||
57 | + if (d) { | ||
58 | + var idx = fs.find(id, onosOrder); | ||
59 | + if (idx >= 0) { | ||
60 | + onosOrder.splice(idx, 1); | ||
61 | + } | ||
62 | + delete onosInstances[id]; | ||
63 | + updateInstances(); | ||
64 | + } else { | ||
65 | + logicError('removeInstance lookup fail. ID = "' + id + '"'); | ||
66 | + } | ||
67 | + } | ||
68 | + | ||
69 | + // ========================== | ||
70 | + | ||
71 | + function clickInst(d) { | ||
72 | + var el = d3.select(this), | ||
73 | + aff = el.classed('affinity'); | ||
74 | + if (!aff) { | ||
75 | + setAffinity(el, d); | ||
76 | + } else { | ||
77 | + cancelAffinity(); | ||
78 | + } | ||
79 | + } | ||
80 | + | ||
81 | + function setAffinity(el, d) { | ||
82 | + d3.selectAll('.onosInst') | ||
83 | + .classed('mastership', true) | ||
84 | + .classed('affinity', false); | ||
85 | + el.classed('affinity', true); | ||
86 | + | ||
87 | + // suppress all elements except nodes whose master is this instance | ||
88 | + api.showMastership(d.id); | ||
89 | + oiShowMaster = true; | ||
90 | + } | ||
91 | + | ||
92 | + function cancelAffinity() { | ||
93 | + d3.selectAll('.onosInst') | ||
94 | + .classed('mastership affinity', false); | ||
95 | + | ||
96 | + api.showMastership(null); | ||
97 | + oiShowMaster = false; | ||
98 | + } | ||
99 | + | ||
100 | + function attachUiBadge(svg) { | ||
101 | + gs.addGlyph(svg, 'uiAttached', 24, true, [14, 54]) | ||
102 | + .classed('badgeIcon uiBadge', true); | ||
103 | + } | ||
104 | + | ||
105 | + function attachReadyBadge(svg) { | ||
106 | + gs.addGlyph(svg, 'checkMark', 16, true, [18, 40]) | ||
107 | + .classed('badgeIcon readyBadge', true); | ||
108 | + } | ||
109 | + | ||
110 | + function instColor(id, online) { | ||
111 | + return sus.cat7().getColor(id, !online, ts.theme()); | ||
112 | + } | ||
113 | + | ||
114 | + // ============================== | ||
115 | + | ||
116 | + function updateInstances() { | ||
117 | + var rox = 5, | ||
118 | + roy = 5, | ||
119 | + rw = 160, | ||
120 | + rhh = 30, | ||
121 | + rbh = 45, | ||
122 | + tx = 48, | ||
123 | + instSvg = { | ||
124 | + width: 170, | ||
125 | + height: 85, | ||
126 | + viewBox: '0 0 170 85' | ||
127 | + }, | ||
128 | + headRect = { | ||
129 | + x: rox, | ||
130 | + y: roy, | ||
131 | + width: rw, | ||
132 | + height: rhh | ||
133 | + }, | ||
134 | + bodyRect = { | ||
135 | + x: rox, | ||
136 | + y: roy + rhh, | ||
137 | + width: rw, | ||
138 | + height: rbh | ||
139 | + }, | ||
140 | + titleAttr = { | ||
141 | + class: 'instTitle', | ||
142 | + x: tx, | ||
143 | + y: 27 | ||
144 | + }; | ||
145 | + | ||
146 | + var onoses = oiBox.el().selectAll('.onosInst') | ||
147 | + .data(onosOrder, function (d) { return d.id; }); | ||
148 | + | ||
149 | + function nSw(n) { | ||
150 | + return 'Devices: ' + n; | ||
151 | + } | ||
152 | + | ||
153 | + // operate on existing onos instances if necessary | ||
154 | + onoses.each(function (d) { | ||
155 | + var el = d3.select(this), | ||
156 | + svg = el.select('svg'); | ||
157 | + | ||
158 | + // update online state | ||
159 | + el.classed('online', d.online); | ||
160 | + el.classed('ready', d.ready); | ||
161 | + | ||
162 | + // update ui-attached state | ||
163 | + svg.select('use.uiBadge').remove(); | ||
164 | + if (d.uiAttached) { | ||
165 | + attachUiBadge(svg); | ||
166 | + } | ||
167 | + | ||
168 | + function updAttr(id, value) { | ||
169 | + svg.select('text.instLabel.' + id).text(value); | ||
170 | + } | ||
171 | + | ||
172 | + updAttr('ip', d.ip); | ||
173 | + updAttr('ns', nSw(d.switches)); | ||
174 | + }); | ||
175 | + | ||
176 | + | ||
177 | + // operate on new onos instances | ||
178 | + var entering = onoses.enter() | ||
179 | + .append('div') | ||
180 | + .classed('onosInst', true) | ||
181 | + .classed('online', function (d) { return d.online; }) | ||
182 | + .classed('ready', function (d) { return d.ready; }) | ||
183 | + .on('click', clickInst); | ||
184 | + | ||
185 | + entering.each(function (d) { | ||
186 | + var el = d3.select(this), | ||
187 | + svg = el.append('svg').attr(instSvg); | ||
188 | + | ||
189 | + svg.append('rect').attr(headRect); | ||
190 | + svg.append('rect').attr(bodyRect); | ||
191 | + | ||
192 | + gs.addGlyph(svg, 'bird', 20, false, [15, 10]) | ||
193 | + .classed('badgeIcon bird', true); | ||
194 | + | ||
195 | + attachReadyBadge(svg); | ||
196 | + | ||
197 | + if (d.uiAttached) { | ||
198 | + attachUiBadge(svg); | ||
199 | + } | ||
200 | + | ||
201 | + svg.append('text') | ||
202 | + .attr(titleAttr) | ||
203 | + .text(d.id); | ||
204 | + | ||
205 | + var ty = 55; | ||
206 | + function addAttr(id, label) { | ||
207 | + svg.append('text').attr({ | ||
208 | + class: 'instLabel ' + id, | ||
209 | + x: tx, | ||
210 | + y: ty | ||
211 | + }).text(label); | ||
212 | + ty += 18; | ||
213 | + } | ||
214 | + | ||
215 | + addAttr('ip', d.ip); | ||
216 | + addAttr('ns', nSw(d.switches)); | ||
217 | + }); | ||
218 | + | ||
219 | + // operate on existing + new onoses here | ||
220 | + // set the affinity colors... | ||
221 | + onoses.each(function (d) { | ||
222 | + | ||
223 | + var el = d3.select(this), | ||
224 | + rect = el.select('svg').select('rect'), | ||
225 | + col = instColor(d.id, d.online); | ||
226 | + | ||
227 | + rect.style('fill', col); | ||
228 | + }); | ||
229 | + | ||
230 | + // adjust the panel size appropriately... | ||
231 | + oiBox.width(instSvg.width * onosOrder.length); | ||
232 | + oiBox.height(instSvg.height); | ||
233 | + | ||
234 | + // remove any outgoing instances | ||
235 | + onoses.exit().remove(); | ||
236 | + } | ||
237 | + | ||
238 | + | ||
239 | + // ========================== | ||
240 | + | ||
241 | + function logicError(msg) { | ||
242 | + if (showLogicErrors) { | ||
243 | + $log.warn('TopoInstService: ' + msg); | ||
244 | + } | ||
245 | + } | ||
246 | + | ||
247 | + function initInst(_api_) { | ||
248 | + api = _api_; | ||
249 | + oiBox = ps.createPanel(idIns, instOpts); | ||
250 | + oiBox.show(); | ||
251 | + | ||
252 | + onosInstances = {}; | ||
253 | + onosOrder = []; | ||
254 | + oiShowMaster = false; | ||
255 | + | ||
256 | + // we want to update the instances, each time the theme changes | ||
257 | + ts.addListener(updateInstances); | ||
258 | + } | ||
259 | + | ||
260 | + function destroyInst() { | ||
261 | + ts.removeListener(updateInstances); | ||
262 | + | ||
263 | + ps.destroyPanel(idIns); | ||
264 | + oiBox = null; | ||
265 | + | ||
266 | + onosInstances = {}; | ||
267 | + onosOrder = []; | ||
268 | + oiShowMaster = false; | ||
269 | + } | ||
270 | + | ||
271 | + function allInstances(data) { | ||
272 | + $log.debug('Update all instances', data); | ||
273 | + | ||
274 | + var members = data.members; | ||
275 | + | ||
276 | + members.forEach(function (member) { | ||
277 | + addInstance(member); | ||
278 | + }); | ||
279 | + } | ||
280 | + | ||
281 | + angular.module('ovTopo2') | ||
282 | + .factory('Topo2InstanceService', | ||
283 | + ['$log', 'PanelService', 'SvgUtilService', 'GlyphService', | ||
284 | + 'ThemeService', 'FnService', 'FlashService', | ||
285 | + | ||
286 | + function (_$log_, _ps_, _sus_, _gs_, _ts_, _fs_, _flash_) { | ||
287 | + $log = _$log_; | ||
288 | + ps = _ps_; | ||
289 | + sus = _sus_; | ||
290 | + gs = _gs_; | ||
291 | + ts = _ts_; | ||
292 | + fs = _fs_; | ||
293 | + flash = _flash_; | ||
294 | + | ||
295 | + return { | ||
296 | + initInst: initInst, | ||
297 | + allInstances: allInstances | ||
298 | + }; | ||
299 | + }]); | ||
300 | + | ||
301 | +}()); |
1 | +/* | ||
2 | + * Copyright 2016-present Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- Topology Links Module. | ||
19 | + Module that holds the links for a region | ||
20 | + */ | ||
21 | + | ||
22 | +(function () { | ||
23 | + 'use strict'; | ||
24 | + | ||
25 | + var Collection, Model; | ||
26 | + | ||
27 | + function createLinkCollection(data, region) { | ||
28 | + | ||
29 | + var LinkCollection = Collection.extend({ | ||
30 | + model: Model | ||
31 | + }); | ||
32 | + | ||
33 | + return new LinkCollection(data); | ||
34 | + } | ||
35 | + | ||
36 | + angular.module('ovTopo2') | ||
37 | + .factory('Topo2LinkService', | ||
38 | + ['Topo2Collection', 'Topo2Model', | ||
39 | + | ||
40 | + function (_Collection_, _Model_) { | ||
41 | + | ||
42 | + Collection = _Collection_; | ||
43 | + Model = _Model_.extend({}); | ||
44 | + | ||
45 | + return { | ||
46 | + createLinkCollection: createLinkCollection | ||
47 | + }; | ||
48 | + } | ||
49 | + ]); | ||
50 | + | ||
51 | +})(); |
1 | +/* | ||
2 | + * Copyright 2016-present Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- Topology Force Module. | ||
19 | + Visualization of the topology in an SVG layer, using a D3 Force Layout. | ||
20 | + */ | ||
21 | + | ||
22 | +(function () { | ||
23 | + 'use strict'; | ||
24 | + | ||
25 | + function Model(attributes) { | ||
26 | + | ||
27 | + var attrs = attributes || {}; | ||
28 | + this.attributes = {}; | ||
29 | + | ||
30 | + attrs = angular.extend({}, attrs); | ||
31 | + this.set(attrs); | ||
32 | + } | ||
33 | + | ||
34 | + Model.prototype = { | ||
35 | + | ||
36 | + get: function (attr) { | ||
37 | + return this.attributes[attr]; | ||
38 | + }, | ||
39 | + | ||
40 | + set: function(data) { | ||
41 | + angular.extend(this.attributes, data); | ||
42 | + }, | ||
43 | + }; | ||
44 | + | ||
45 | + | ||
46 | + Model.extend = function (protoProps, staticProps) { | ||
47 | + | ||
48 | + var parent = this; | ||
49 | + var child; | ||
50 | + | ||
51 | + child = function () { | ||
52 | + return parent.apply(this, arguments); | ||
53 | + }; | ||
54 | + | ||
55 | + angular.extend(child, parent, staticProps); | ||
56 | + | ||
57 | + // Set the prototype chain to inherit from `parent`, without calling | ||
58 | + // `parent`'s constructor function and add the prototype properties. | ||
59 | + child.prototype = angular.extend({}, parent.prototype, protoProps); | ||
60 | + child.prototype.constructor = child; | ||
61 | + | ||
62 | + // Set a convenience property in case the parent's prototype is needed | ||
63 | + // later. | ||
64 | + child.__super__ = parent.prototype; | ||
65 | + | ||
66 | + return child; | ||
67 | + }; | ||
68 | + | ||
69 | + angular.module('ovTopo2') | ||
70 | + .factory('Topo2Model', | ||
71 | + [ | ||
72 | + function () { | ||
73 | + return Model; | ||
74 | + } | ||
75 | + ]); | ||
76 | + | ||
77 | +})(); |
1 | +/* | ||
2 | + * Copyright 2016-present Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- Topology Region Module. | ||
19 | + Module that holds the current region in memory | ||
20 | + */ | ||
21 | + | ||
22 | +(function () { | ||
23 | + 'use strict'; | ||
24 | + | ||
25 | + var $log, | ||
26 | + wss, | ||
27 | + t2sr, | ||
28 | + t2ds, | ||
29 | + t2hs, | ||
30 | + t2ls; | ||
31 | + | ||
32 | + var regions; | ||
33 | + | ||
34 | + function init() { | ||
35 | + regions = {}; | ||
36 | + } | ||
37 | + | ||
38 | + function addRegion(data) { | ||
39 | + | ||
40 | + var region = { | ||
41 | + subregions: t2sr.createSubRegionCollection(data.subregions), | ||
42 | + devices: t2ds.createDeviceCollection(data.devices, data), | ||
43 | + hosts: t2hs.createHostCollection(data.hosts), | ||
44 | + links: t2ls.createLinkCollection(data.links), | ||
45 | + }; | ||
46 | + | ||
47 | + $log.debug('Region: ', region); | ||
48 | + } | ||
49 | + | ||
50 | + angular.module('ovTopo2') | ||
51 | + .factory('Topo2RegionService', | ||
52 | + ['$log', 'WebSocketService', 'Topo2SubRegionService', 'Topo2DeviceService', | ||
53 | + 'Topo2HostService', 'Topo2LinkService', | ||
54 | + | ||
55 | + function (_$log_, _wss_, _t2sr_, _t2ds_, _t2hs_, _t2ls_) { | ||
56 | + | ||
57 | + $log = _$log_; | ||
58 | + wss = _wss_; | ||
59 | + t2sr = _t2sr_; | ||
60 | + t2ds = _t2ds_; | ||
61 | + t2hs = _t2hs_; | ||
62 | + t2ls = _t2ls_; | ||
63 | + | ||
64 | + return { | ||
65 | + init: init, | ||
66 | + | ||
67 | + addRegion: addRegion, | ||
68 | + getSubRegions: t2sr.getSubRegions | ||
69 | + }; | ||
70 | + }]); | ||
71 | + | ||
72 | +})(); |
1 | +/* | ||
2 | + * Copyright 2016-present Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- Topology SubRegion Module. | ||
19 | + Module that holds the sub-regions for a region | ||
20 | + */ | ||
21 | + | ||
22 | +(function () { | ||
23 | + 'use strict'; | ||
24 | + | ||
25 | + var Collection, Model; | ||
26 | + | ||
27 | + function createSubRegionCollection(data, region) { | ||
28 | + | ||
29 | + var SubRegionCollection = Collection.extend({ | ||
30 | + model: Model | ||
31 | + }); | ||
32 | + | ||
33 | + return new SubRegionCollection(data); | ||
34 | + } | ||
35 | + | ||
36 | + angular.module('ovTopo2') | ||
37 | + .factory('Topo2SubRegionService', | ||
38 | + ['Topo2Collection', 'Topo2Model', | ||
39 | + | ||
40 | + function (_Collection_, _Model_) { | ||
41 | + | ||
42 | + Collection = _Collection_; | ||
43 | + Model = _Model_.extend({}); | ||
44 | + | ||
45 | + return { | ||
46 | + createSubRegionCollection: createSubRegionCollection | ||
47 | + }; | ||
48 | + } | ||
49 | + ]); | ||
50 | + | ||
51 | +})(); |
... | @@ -127,8 +127,16 @@ | ... | @@ -127,8 +127,16 @@ |
127 | 127 | ||
128 | <!-- Under development for Region support. --> | 128 | <!-- Under development for Region support. --> |
129 | <script src="app/view/topo2/topo2.js"></script> | 129 | <script src="app/view/topo2/topo2.js"></script> |
130 | + <script src="app/view/topo2/topo2Collection.js"></script> | ||
131 | + <script src="app/view/topo2/topo2Device.js"></script> | ||
132 | + <script src="app/view/topo2/topo2Model.js"></script> | ||
130 | <script src="app/view/topo2/topo2Event.js"></script> | 133 | <script src="app/view/topo2/topo2Event.js"></script> |
131 | <script src="app/view/topo2/topo2Force.js"></script> | 134 | <script src="app/view/topo2/topo2Force.js"></script> |
135 | + <script src="app/view/topo2/topo2Host.js"></script> | ||
136 | + <script src="app/view/topo2/topo2Instance.js"></script> | ||
137 | + <script src="app/view/topo2/topo2Link.js"></script> | ||
138 | + <script src="app/view/topo2/topo2Region.js"></script> | ||
139 | + <script src="app/view/topo2/topo2SubRegion.js"></script> | ||
132 | <link rel="stylesheet" href="app/view/topo2/topo2.css"> | 140 | <link rel="stylesheet" href="app/view/topo2/topo2.css"> |
133 | <link rel="stylesheet" href="app/view/topo2/topo2-theme.css"> | 141 | <link rel="stylesheet" href="app/view/topo2/topo2-theme.css"> |
134 | 142 | ... | ... |
-
Please register or login to post a comment