blob: e9829e0f053e5e708284cbdfabc51413d86153ca [file] [log] [blame]
Simon Hunt08f841d02015-02-10 14:39:20 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Simon Hunt08f841d02015-02-10 14:39:20 -08003 *
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 Selection Module.
19 Defines behavior when selecting nodes.
20 */
21
22(function () {
23 'use strict';
24
25 // injected refs
Simon Hunta58d8942017-08-11 12:51:14 -070026 var $log, fs, wss, tov, tps, tts, sus;
Simon Hunt08f841d02015-02-10 14:39:20 -080027
28 // api to topoForce
29 var api;
30 /*
31 node() // get ref to D3 selection of nodes
32 zoomingOrPanning( ev )
33 updateDeviceColors( [dev] )
Prince Pereira46c82d42016-09-19 13:30:50 +053034 deselectAllLinks()
Simon Hunt08f841d02015-02-10 14:39:20 -080035 */
36
37 // internal state
Steven Burrows041c1aa2016-04-12 15:45:05 +010038 var hovered, selections, selectOrder, consumeClick;
39
Steven Burrows1c2a9682017-07-14 16:52:46 +010040 function setInitialState() {
41 hovered = null; // the node over which the mouse is hovering
42 selections = {}; // currently selected nodes (by id)
43 selectOrder = []; // the order in which we made selections
44 consumeClick = false; // used to coordinate with SVG click handler
Steven Burrows041c1aa2016-04-12 15:45:05 +010045 }
Simon Hunt08f841d02015-02-10 14:39:20 -080046
47 // ==========================
48
49 function nSel() {
50 return selectOrder.length;
51 }
52 function getSel(idx) {
53 return selections[selectOrder[idx]];
54 }
55 function allSelectionsClass(cls) {
56 for (var i=0, n=nSel(); i<n; i++) {
57 if (getSel(i).obj.class !== cls) {
58 return false;
59 }
60 }
61 return true;
62 }
63
64 // ==========================
65
66 function nodeMouseOver(m) {
67 if (!m.dragStarted) {
Simon Hunt08f841d02015-02-10 14:39:20 -080068 if (hovered != m) {
69 hovered = m;
Simon Hunt584e92d2015-08-24 11:27:22 -070070 tov.hooks.mouseOver({
71 id: m.id,
72 class: m.class,
Steven Burrows1c2a9682017-07-14 16:52:46 +010073 type: m.type,
Simon Hunt584e92d2015-08-24 11:27:22 -070074 });
Simon Hunt08f841d02015-02-10 14:39:20 -080075 }
76 }
77 }
78
79 function nodeMouseOut(m) {
80 if (!m.dragStarted) {
81 if (hovered) {
82 hovered = null;
Simon Hunt584e92d2015-08-24 11:27:22 -070083 tov.hooks.mouseOut();
Simon Hunt08f841d02015-02-10 14:39:20 -080084 }
Simon Hunt08f841d02015-02-10 14:39:20 -080085 }
86 }
87
88 // ==========================
89
90 function selectObject(obj) {
91 var el = this,
Simon Hunt5aac2fc2015-06-09 12:34:07 -070092 nodeEv = el && el.tagName === 'g',
93 ev = d3.event.sourceEvent || {},
Simon Hunt08f841d02015-02-10 14:39:20 -080094 n;
95
96 if (api.zoomingOrPanning(ev)) {
97 return;
98 }
99
Simon Hunt5aac2fc2015-06-09 12:34:07 -0700100 if (nodeEv) {
Simon Hunt08f841d02015-02-10 14:39:20 -0800101 n = d3.select(el);
102 } else {
103 api.node().each(function (d) {
104 if (d == obj) {
105 n = d3.select(el = this);
106 }
107 });
108 }
Prince Pereira46c82d42016-09-19 13:30:50 +0530109
Simon Huntf9761452016-11-19 09:06:17 -0800110 if (obj && obj.class === 'link') {
111 if (selections[obj.key]) {
112 deselectObject(obj.key);
113 } else {
114 selections[obj.key] = { obj: obj, el: el };
115 selectOrder.push(obj.key);
Prince Pereira46c82d42016-09-19 13:30:50 +0530116 }
Simon Huntf9761452016-11-19 09:06:17 -0800117 updateDetail();
118 return;
Prince Pereira46c82d42016-09-19 13:30:50 +0530119 }
120
121 if (!n) {
122 return;
123 }
Simon Hunt08f841d02015-02-10 14:39:20 -0800124
Simon Hunt5aac2fc2015-06-09 12:34:07 -0700125 if (nodeEv) {
126 consumeClick = true;
127 }
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700128
Simon Hunt08f841d02015-02-10 14:39:20 -0800129 if (ev.shiftKey && n.classed('selected')) {
130 deselectObject(obj.id);
131 updateDetail();
132 return;
133 }
134
135 if (!ev.shiftKey) {
Simon Hunta17fa672015-08-19 18:42:22 -0700136 deselectAll(true);
Simon Hunt08f841d02015-02-10 14:39:20 -0800137 }
138
139 selections[obj.id] = { obj: obj, el: el };
140 selectOrder.push(obj.id);
141
142 n.classed('selected', true);
Simon Hunt4766dfb2016-06-14 17:16:22 -0700143 if (n.classed('device')) {
144 api.updateDeviceColors(obj);
145 }
Simon Hunt08f841d02015-02-10 14:39:20 -0800146 updateDetail();
Simon Hunt08f841d02015-02-10 14:39:20 -0800147 }
148
Simon Hunt7faabd52016-08-18 16:16:19 -0700149 function reselect() {
150 selectOrder.forEach(function (id) {
151 var sel = d3.select('g#' + sus.safeId(id));
152 sel.classed('selected', true);
153 });
154 updateDetail();
155 }
156
Simon Hunt08f841d02015-02-10 14:39:20 -0800157 function deselectObject(id) {
158 var obj = selections[id];
159 if (obj) {
160 d3.select(obj.el).classed('selected', false);
161 delete selections[id];
162 fs.removeFromArray(id, selectOrder);
163 api.updateDeviceColors(obj.obj);
164 }
Simon Hunt08f841d02015-02-10 14:39:20 -0800165 }
166
Simon Hunta17fa672015-08-19 18:42:22 -0700167 function deselectAll(skipUpdate) {
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700168 var something = (selectOrder.length > 0);
169
Simon Hunt08f841d02015-02-10 14:39:20 -0800170 // deselect all nodes in the network...
171 api.node().classed('selected', false);
172 selections = {};
173 selectOrder = [];
174 api.updateDeviceColors();
Simon Hunta17fa672015-08-19 18:42:22 -0700175 if (!skipUpdate) {
176 updateDetail();
177 }
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700178
179 // return true if something was selected
180 return something;
Simon Hunt08f841d02015-02-10 14:39:20 -0800181 }
182
183 // === -----------------------------------------------------
184
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700185 function requestDetails(data) {
Simon Hunta58d8942017-08-11 12:51:14 -0700186 var itemClass = data.class,
187 payload = {
188 class: itemClass,
189 id: data.id,
190 };
191
192 // special handling for links...
193 if (itemClass === 'link') {
194 payload.key = data.key;
195 if (data.source.class === 'host') {
196 payload.isEdgeLink = true;
197 payload.sourceId = data.source.id;
198 payload.targetId = data.source.cp.device;
199 payload.targetPort = data.source.cp.port;
200 } else {
201 payload.isEdgeLink = false;
202 payload.sourceId = data.source.id;
203 payload.sourcePort = data.srcPort;
204 payload.targetId = data.target.id;
205 payload.targetPort = data.tgtPort;
206 }
207 }
208
209 $log.debug('EVENT> requestDetails', payload);
210 wss.sendEvent('requestDetails', payload);
Simon Hunt08f841d02015-02-10 14:39:20 -0800211 }
212
213 // === -----------------------------------------------------
214
215 function updateDetail() {
216 var nSel = selectOrder.length;
217 if (!nSel) {
218 emptySelect();
219 } else if (nSel === 1) {
220 singleSelect();
221 } else {
222 multiSelect();
223 }
224 }
225
226 function emptySelect() {
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700227 tov.hooks.emptySelect();
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700228 tps.displayNothing();
Simon Hunt08f841d02015-02-10 14:39:20 -0800229 }
230
231 function singleSelect() {
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700232 var data = getSel(0).obj;
Prince Pereira46c82d42016-09-19 13:30:50 +0530233
Simon Hunta58d8942017-08-11 12:51:14 -0700234 $log.debug('Requesting details from server for', data);
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700235 requestDetails(data);
236 // NOTE: detail panel is shown as a response to receiving
237 // a 'showDetails' event from the server. See 'showDetails'
238 // callback function below...
Simon Hunt08f841d02015-02-10 14:39:20 -0800239 }
240
241 function multiSelect() {
Simon Hunt08f841d02015-02-10 14:39:20 -0800242 // display the selected nodes in the detail panel
243 tps.displayMulti(selectOrder);
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700244 addHostSelectionActions();
245 tov.hooks.multiSelect(selectOrder);
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700246 tps.displaySomething();
Simon Hunt08f841d02015-02-10 14:39:20 -0800247 }
248
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700249 function addHostSelectionActions() {
250 if (allSelectionsClass('host')) {
251 if (nSel() === 2) {
252 tps.addAction({
253 id: 'host-flow-btn',
254 gid: 'endstation',
255 cb: tts.addHostIntent,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100256 tt: 'Create Host-to-Host Flow',
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700257 });
258 } else if (nSel() >= 2) {
259 tps.addAction({
260 id: 'mult-src-flow-btn',
261 gid: 'flows',
262 cb: tts.addMultiSourceIntent,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100263 tt: 'Create Multi-Source Flow',
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700264 });
265 }
266 }
267 }
268
Simon Hunt08f841d02015-02-10 14:39:20 -0800269
270 // === -----------------------------------------------------
271 // Event Handlers
272
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700273 // display the data for the single selected node
Simon Hunt08f841d02015-02-10 14:39:20 -0800274 function showDetails(data) {
Simon Hunt3a0598f2015-08-04 19:59:04 -0700275 var buttons = fs.isA(data.buttons) || [];
Simon Hunt08f841d02015-02-10 14:39:20 -0800276 tps.displaySingle(data);
Simon Hunt879ce452017-08-10 23:32:00 -0700277 tov.installButtons(buttons, data, data.propValues['uri']);
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700278 tov.hooks.singleSelect(data);
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700279 tps.displaySomething();
Simon Hunt6036b192015-02-11 11:20:26 -0800280 }
281
Simon Huntd2862c32015-08-24 17:41:51 -0700282 // returns true if one or more nodes are selected.
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700283 function somethingSelected() {
Simon Huntd2862c32015-08-24 17:41:51 -0700284 return nSel();
Simon Hunt08f841d02015-02-10 14:39:20 -0800285 }
Simon Hunt08f841d02015-02-10 14:39:20 -0800286
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700287 function clickConsumed(x) {
288 var cc = consumeClick;
289 consumeClick = !!x;
290 return cc;
291 }
292
Simon Hunt8d558082015-10-29 21:32:50 -0700293 // returns a selection context, providing info about what is selected
294 function selectionContext() {
295 var devices = [],
296 hosts = [],
297 types = {};
298
299 angular.forEach(selections, function (d) {
300 var o = d.obj,
301 c = o.class;
302
303 if (c === 'device') {
304 devices.push(o.id);
305 types[o.id] = o.type;
306 }
307 if (c === 'host') {
308 hosts.push(o.id);
309 types[o.id] = o.type;
310 }
311 });
312
313 return {
314 devices: devices,
315 hosts: hosts,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100316 types: types,
Simon Hunt8d558082015-10-29 21:32:50 -0700317 };
318 }
319
Simon Hunt08f841d02015-02-10 14:39:20 -0800320 // === -----------------------------------------------------
321 // === MODULE DEFINITION ===
322
323 angular.module('ovTopo')
Simon Hunt75ec9692015-02-11 16:40:36 -0800324 .factory('TopoSelectService',
Simon Hunta58d8942017-08-11 12:51:14 -0700325 ['$log', 'FnService', 'WebSocketService', 'TopoOverlayService',
Steven Burrows1c2a9682017-07-14 16:52:46 +0100326 'TopoPanelService', 'TopoTrafficService', 'SvgUtilService',
Simon Hunt08f841d02015-02-10 14:39:20 -0800327
Simon Hunta58d8942017-08-11 12:51:14 -0700328 function (_$log_, _fs_, _wss_, _tov_, _tps_, _tts_, _sus_) {
329 $log = _$log_;
Simon Hunt6036b192015-02-11 11:20:26 -0800330 fs = _fs_;
Simon Hunt237676b52015-03-10 19:04:26 -0700331 wss = _wss_;
Simon Huntfb940112015-07-29 18:36:35 -0700332 tov = _tov_;
Simon Hunt6036b192015-02-11 11:20:26 -0800333 tps = _tps_;
Simon Huntf542d842015-02-11 16:20:33 -0800334 tts = _tts_;
Simon Hunt7faabd52016-08-18 16:16:19 -0700335 sus = _sus_;
Simon Hunt08f841d02015-02-10 14:39:20 -0800336
Simon Hunt6036b192015-02-11 11:20:26 -0800337 function initSelect(_api_) {
338 api = _api_;
Simon Hunt7faabd52016-08-18 16:16:19 -0700339 if (!selections) {
340 setInitialState();
341 }
Simon Hunt6036b192015-02-11 11:20:26 -0800342 }
Simon Hunt08f841d02015-02-10 14:39:20 -0800343
Simon Hunt6036b192015-02-11 11:20:26 -0800344 function destroySelect() { }
Simon Hunt08f841d02015-02-10 14:39:20 -0800345
Simon Hunt6036b192015-02-11 11:20:26 -0800346 return {
347 initSelect: initSelect,
348 destroySelect: destroySelect,
Simon Hunt08f841d02015-02-10 14:39:20 -0800349
Simon Hunt6036b192015-02-11 11:20:26 -0800350 showDetails: showDetails,
Simon Hunt08f841d02015-02-10 14:39:20 -0800351
Simon Hunt6036b192015-02-11 11:20:26 -0800352 nodeMouseOver: nodeMouseOver,
353 nodeMouseOut: nodeMouseOut,
354 selectObject: selectObject,
355 deselectObject: deselectObject,
356 deselectAll: deselectAll,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700357 updateDetail: updateDetail,
Simon Huntf542d842015-02-11 16:20:33 -0800358
Simon Hunta0eb0a82015-02-11 12:30:06 -0800359 hovered: function () { return hovered; },
Simon Huntf542d842015-02-11 16:20:33 -0800360 selectOrder: function () { return selectOrder; },
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700361 somethingSelected: somethingSelected,
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700362
Simon Hunt8d558082015-10-29 21:32:50 -0700363 clickConsumed: clickConsumed,
Simon Hunt7faabd52016-08-18 16:16:19 -0700364 selectionContext: selectionContext,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100365 reselect: reselect,
Simon Hunt6036b192015-02-11 11:20:26 -0800366 };
367 }]);
Simon Hunt08f841d02015-02-10 14:39:20 -0800368}());