blob: 502b43821043c1a7f6eaeff2918ea887a338a0e7 [file] [log] [blame]
Yi Tsenga87b40c2017-09-10 00:59:03 -07001/*
2 * Copyright 2017-present Open Networking Foundation
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 -- Pipeconf View Module
19 */
20
21(function () {
22 'use strict';
23
24 // injected refs
25 var $log, $scope, $loc, $interval, $timeout, fs, ns, wss, ls, ps, mast, is, dps;
26
27 // Constants
28 var pipeconfRequest = "pipeconfRequest",
29 pipeConfResponse = "pipeConfResponse",
30 noPipeconfResp = "noPipeconfResp",
31 invalidDevId = "invalidDevId",
32 pipeconf = "pipeconf",
33 pipelineModel = "pipelineModel",
34 devId = "devId",
35 topPdg = 28,
36 pName = 'pipeconf-detail-panel',
37 refreshRate = 5000;
38
39 // For request handling
40 var handlers,
41 refreshPromise;
42
43 // Details panel
44 var pWidth = 600,
45 pTopHeight = 111,
46 pStartY,
47 wSize,
48 pHeight,
49 detailsPanel;
50
51 // create key bindings to handle panel
52 var keyBindings = {
53 esc: [closePanel, 'Close the details panel'],
54 _helpFormat: ['esc'],
55 };
56
57 function fetchPipeconfData() {
58 if ($scope.autoRefresh && wss.isConnected() && !ls.waiting()) {
59 ls.start();
60 var requestData = {
61 devId: $scope.devId
62 };
63 wss.sendEvent(pipeconfRequest, requestData);
64 }
65 }
66
67 function pipeConfRespCb(data) {
68 ls.stop();
69 if (!data.hasOwnProperty(pipeconf)) {
70 $scope.pipeconf = null;
71 return;
72 }
73 $scope.pipeconf = data[pipeconf];
74 $scope.pipelineModel = data[pipelineModel];
75 $scope.$apply();
76 }
77
78 function noPipeconfRespCb(data) {
79 ls.stop();
80 $scope.pipeconf = null;
81 $scope.pipelineModel = null;
82 $scope.$apply();
83 }
84
85 function viewDestroy() {
86 wss.unbindHandlers(handlers);
87 $interval.cancel(refreshPromise);
88 refreshPromise = null;
89 ls.stop();
90 }
91
92 function headerSelectCb($event, header) {
93 if ($scope.selectedId !== null &&
94 $scope.selectedId.type === 'header' &&
95 $scope.selectedId.name === header.name) {
96
97 // Hide the panel when select same row
98 closePanel();
99 return;
100 }
101
102 $scope.selectedId = {
103 type: 'header',
104 name: header.name,
105 };
106
107 var subtitles = [
108 {
109 label: 'Header Type: ',
110 value: header.type.name,
111 },
112 {
113 label: 'Is metadata: ',
114 value: header.isMetadata,
115 },
116 {
117 label: 'Index: ',
118 value: header.index,
119 },
120 ];
121
122 var tables = [
123 {
124 title: 'Fields',
125 headers: ['Name', 'Bit width'],
126 data: header.type.fields,
127 noDataText: 'No header fields'
128 },
129 ];
130 populateDetailPanel(header.name, subtitles, tables);
131 }
132
133 function actionSelectCb($event, action) {
134 if ($scope.selectedId !== null &&
135 $scope.selectedId.type === 'action' &&
136 $scope.selectedId.name === action.name) {
137
138 // Hide the panel when select same row
139 closePanel();
140 return;
141 }
142
143 $scope.selectedId = {
144 type: 'action',
145 name: action.name,
146 };
147
148 var subtitles = [];
149 var tables = [
150 {
151 title: 'Parameters',
152 headers: ['Name', 'Bit width'],
153 data: action.params,
154 noDataText: 'No action parameters',
155 },
156 ];
157
158 populateDetailPanel(action.name, subtitles, tables);
159 }
160
161 function tableSelectCb($event, table) {
162 if ($scope.selectedId !== null &&
163 $scope.selectedId.type === 'table' &&
164 $scope.selectedId.name === table.name) {
165
166 // Hide the panel when select same row
167 closePanel();
168 return;
169 }
170
171 $scope.selectedId = {
172 type: 'table',
173 name: table.name,
174 };
175
176 var subtitles = [
177 {
178 label: 'Max Size: ',
179 value: table.maxSize,
180 },
181 {
182 label: 'Has counters: ',
183 value: table.hasCounters,
184 },
185 {
186 label: 'Support Aging: ',
187 value: table.supportAging,
188 },
189 ];
190
191 var matchFields = table.matchFields.map(function(mp) {
192 return {
Yi Tsenge2a15002017-11-29 16:42:52 -0800193 name: mp.field,
194 bitWidth: mp.bitWidth,
Yi Tsenga87b40c2017-09-10 00:59:03 -0700195 matchType: mp.matchType,
196 }
197 });
198
199 var tables = [
200 {
201 title: 'Match fields',
202 headers: ['Name', 'Bit width', 'Match type'],
203 data: matchFields,
204 noDataText: 'No match fields'
205 },
206 {
207 title: 'Actions',
208 headers: ['Name'],
209 data: table.actions,
210 noDataText: 'No actions'
211 },
212 ];
213
214 populateDetailPanel(table.name, subtitles, tables);
215 }
216
217 function closePanel() {
218 if (detailsPanel.isVisible()) {
219
220 detailsPanel.hide();
221
222 // Avoid Angular inprog error
223 $timeout(function() {
224 $scope.selectedId = null;
225 }, 0);
226 return true;
227 }
228 return false;
229 }
230
231 function populateDetailTable(tableContainer, table) {
232 var tableTitle = table.title;
233 var tableData = table.data;
234 var tableHeaders = table.headers;
235 var noDataText = table.noDataText;
236
237 tableContainer.append('h2').classed('detail-panel-bottom-title', true).text(tableTitle);
238
239 var detailPanelTable = tableContainer.append('table').classed('detail-panel-table', true);
240 var headerTr = detailPanelTable.append('tr').classed('detail-panel-table-header', true);
241
242 tableHeaders.forEach(function(h) {
243 headerTr.append('th').text(h);
244 });
245
246 if (tableData.length === 0) {
247 var row = detailPanelTable.append('tr').classed('detail-panel-table-row', true);
248 row.append('td')
249 .classed('detail-panel-table-col no-data', true)
250 .attr('colspan', tableHeaders.length)
251 .text(noDataText);
252 }
253
254 tableData.forEach(function(data) {
255 var row = detailPanelTable.append('tr').classed('detail-panel-table-row', true);
256 if (fs.isS(data)) {
257 row.append('td').classed('detail-panel-table-col', true).text(data);
258 } else {
259 Object.keys(data).forEach(function(k) {
260 row.append('td').classed('detail-panel-table-col', true).text(data[k]);
261 });
262 }
263 });
264
265 tableContainer.append('hr');
266 }
267
268 function populateDetailTables(tableContainer, tables) {
269 tables.forEach(function(table) {
270 populateDetailTable(tableContainer, table);
271 })
272 }
273
274 function populateDetailPanel(topTitle, topSubtitles, tables) {
275 dps.empty();
276 dps.addContainers();
277 dps.addCloseButton(closePanel);
278
279 var top = dps.top();
280 top.append('h2').classed('detail-panel-header', true).text(topTitle);
281 topSubtitles.forEach(function(st) {
282 var typeText = top.append('div').classed('top-info', true);
283 typeText.append('p').classed('label', true).text(st.label);
284 typeText.append('p').classed('value', true).text(st.value);
285 });
286
287 var bottom = dps.bottom();
288 var bottomHeight = pHeight - pTopHeight - 60;
289 bottom.style('height', bottomHeight + 'px');
290 populateDetailTables(bottom, tables);
291
292 detailsPanel.width(pWidth);
293 detailsPanel.show();
294 resizeDetailPanel();
295 }
296
297 function heightCalc() {
298 pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
299 + mast.mastHeight() + topPdg;
300 wSize = fs.windowSize(pStartY);
301 pHeight = wSize.height - 20;
302 }
303
304 function resizeDetailPanel() {
305 if (detailsPanel.isVisible()) {
306 heightCalc();
307 var bottomHeight = pHeight - pTopHeight - 60;
308 d3.select('.bottom').style('height', bottomHeight + 'px');
309 detailsPanel.height(pHeight);
310 }
311 }
312
313 angular.module('ovPipeconf', [])
314 .controller('OvPipeconfCtrl',
315 ['$log', '$scope', '$location', '$interval', '$timeout', 'FnService', 'NavService', 'WebSocketService',
316 'LoadingService', 'PanelService', 'MastService', 'IconService', 'DetailsPanelService',
317 function (_$log_, _$scope_, _$loc_, _$interval_, _$timeout_, _fs_,
318 _ns_, _wss_, _ls_, _ps_, _mast_, _is_, _dps_) {
319 $log = _$log_;
320 $scope = _$scope_;
321 $loc = _$loc_;
322 $interval = _$interval_;
323 $timeout = _$timeout_;
324 fs = _fs_;
325 ns = _ns_;
326 wss = _wss_;
327 ls = _ls_;
328 ps = _ps_;
329 mast = _mast_;
330 is = _is_;
331 dps = _dps_;
332
333 $scope.deviceTip = 'Show device table';
334 $scope.flowTip = 'Show flow view for this device';
335 $scope.portTip = 'Show port view for this device';
336 $scope.groupTip = 'Show group view for this device';
337 $scope.meterTip = 'Show meter view for selected device';
338 $scope.pipeconfTip = 'Show pipeconf view for selected device';
339
340 var params = $loc.search();
341 if (params.hasOwnProperty(devId)) {
342 $scope.devId = params[devId];
343 }
344 $scope.nav = function (path) {
345 if ($scope.devId) {
346 ns.navTo(path, { devId: $scope.devId });
347 }
348 };
349 handlers = {
350 pipeConfResponse: pipeConfRespCb,
351 noPipeconfResp: noPipeconfRespCb,
352 invalidDevId: noPipeconfRespCb,
353 };
354 wss.bindHandlers(handlers);
355 $scope.$on('$destroy', viewDestroy);
356
357 $scope.autoRefresh = true;
358 fetchPipeconfData();
359
360 // On click callbacks, initialize select id
361 $scope.selectedId = null;
362 $scope.headerSelectCb = headerSelectCb;
363 $scope.actionSelectCb = actionSelectCb;
364 $scope.tableSelectCb = tableSelectCb;
365
366 // Make them collapsable
367 $scope.collapsePipeconf = false;
368 $scope.collapseHeaders = false;
369 $scope.collapseActions = false;
370 $scope.collapseTables = false;
371
372 $scope.mapToNames = function(data) {
373 return data.map(function(d) {
374 return d.name;
375 });
376 };
377
378 $scope.matMatchFields = function(matchFields) {
379 return matchFields.map(function(mf) {
Yi Tsenge2a15002017-11-29 16:42:52 -0800380 return mf.field;
Yi Tsenga87b40c2017-09-10 00:59:03 -0700381 });
382 };
383
384 refreshPromise = $interval(function() {
385 fetchPipeconfData();
386 }, refreshRate);
387
388 $log.log('OvPipeconfCtrl has been created');
389 }])
390 .directive('autoHeight', ['$window', 'FnService',
391 function($window, fs) {
392 return function(scope, element) {
393 var autoHeightElem = d3.select(element[0]);
394
395 scope.$watchCollection(function() {
396 return {
397 h: $window.innerHeight
398 };
399 }, function() {
400 var wsz = fs.windowSize(140, 0);
401 autoHeightElem.style('height', wsz.height + 'px');
402 });
403 };
404 }
405 ])
406 .directive('pipeconfViewDetailPanel', ['$rootScope', '$window', '$timeout', 'KeyService',
407 function($rootScope, $window, $timeout, ks) {
408 function createDetailsPanel() {
409 detailsPanel = dps.create(pName, {
410 width: wSize.width,
411 margin: 0,
412 hideMargin: 0,
413 scope: $scope,
414 keyBindings: keyBindings,
415 nameChangeRequest: null,
416 });
417 $scope.hidePanel = function () { detailsPanel.hide(); };
418 detailsPanel.hide();
419 }
420
421 function initPanel() {
422 heightCalc();
423 createDetailsPanel();
424 }
425
426 return function(scope) {
427 var unbindWatch;
428 // Safari has a bug where it renders the fixed-layout table wrong
429 // if you ask for the window's size too early
430 if (scope.onos.browser === 'safari') {
431 $timeout(initPanel);
432 } else {
433 initPanel();
434 }
435
436 // if the panelData changes
437 scope.$watch('panelData', function () {
438 if (!fs.isEmptyObject(scope.panelData)) {
439 // populateDetails(scope.panelData);
440 detailsPanel.show();
441 }
442 });
443
444 // if the window size changes
445 unbindWatch = $rootScope.$watchCollection(
446 function () {
447 return {
448 h: $window.innerHeight,
449 w: $window.innerWidth,
450 };
451 }, function () {
452 resizeDetailPanel();
453 }
454 );
455
456 scope.$on('$destroy', function () {
457 unbindWatch();
458 ks.unbindKeys();
459 ps.destroyPanel(pName);
460 });
461 };
462 }
463 ]);
464}());