blob: 4938eafec9f6bd84b449d3706ee3d0db219dbbf5 [file] [log] [blame]
Thomas Vachuska0fa583c2015-03-30 23:07:41 -07001/*
Jian Lia54de5a2016-01-20 23:10:39 -08002 * Copyright 2015-2016 Open Networking Laboratory
Thomas Vachuska0fa583c2015-03-30 23:07:41 -07003 *
Thomas Vachuskaa7a0f562015-04-14 23:27:44 -07004 * Licensed under the Apache License, Version 2.0 (the 'License');
Thomas Vachuska0fa583c2015-03-30 23:07:41 -07005 * 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
Thomas Vachuskaa7a0f562015-04-14 23:27:44 -070011 * distributed under the License is distributed on an 'AS IS' BASIS,
Thomas Vachuska0fa583c2015-03-30 23:07:41 -070012 * 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 -- App View Module
19 */
20
21(function () {
22 'use strict';
23
Jian Lia54de5a2016-01-20 23:10:39 -080024 // injected refs
25 var $log, $scope, $loc, fs, ps, wss, is, ns, ks, is;
26
27 // internal state
28 var detailsPanel,
29 pStartY,
30 pHeight,
31 top,
Jian Lida253e02016-01-21 17:46:17 -080032 middle,
Jian Lia54de5a2016-01-20 23:10:39 -080033 bottom,
34 iconDiv,
35 wSize = false;
36
Bri Prebilic Cole522e7562015-06-22 15:56:25 -070037 // constants
38 var INSTALLED = 'INSTALLED',
39 ACTIVE = 'ACTIVE',
Simon Hunt8d28a552016-01-11 14:01:02 -080040 appMgmtReq = 'appManagementRequest',
Jian Lia54de5a2016-01-20 23:10:39 -080041 topPdg = 50,
42 ctnrPdg = 24,
Jian Lida253e02016-01-21 17:46:17 -080043 tbWidth = 470,
Jian Lia54de5a2016-01-20 23:10:39 -080044 scrollSize = 17,
45 pName = 'application-details-panel',
46 detailsReq = 'appDetailsRequest',
47 detailsResp = 'appDetailsResponse',
Simon Hunt8d28a552016-01-11 14:01:02 -080048 fileUploadUrl = 'applications/upload',
Jian Lia54de5a2016-01-20 23:10:39 -080049 iconUrlPrefix = 'rs/applications/',
50 iconUrlSuffix = '/icon',
Simon Hunt8d28a552016-01-11 14:01:02 -080051 dialogId = 'app-dialog',
52 dialogOpts = {
53 edge: 'right'
Simon Hunt3f92c432016-01-12 17:34:23 -080054 },
55 strongWarning = {
56 'org.onosproject.drivers': true
57 },
58 discouragement = 'Deactivating or uninstalling this component can' +
Jian Lia54de5a2016-01-20 23:10:39 -080059 ' have serious negative consequences! Do so at your own risk!!',
60 propOrder = ['id', 'state', 'category', 'version', 'origin', 'role', 'url'],
61 friendlyProps = ['App ID', 'State', 'Category', 'Version', 'Origin', 'Role', 'URL'];
62
63 function createDetailsPane() {
64 detailsPanel = ps.createPanel(pName, {
65 width: wSize.width,
66 margin: 0,
67 hideMargin: 0
68 });
69 detailsPanel.el().style({
70 position: 'absolute',
71 top: pStartY + 'px'
72 });
73 $scope.hidePanel = function () { detailsPanel.hide(); };
74 detailsPanel.hide();
75 }
76
77 function closePanel() {
78 if (detailsPanel.isVisible()) {
79 $scope.selId = null;
80 detailsPanel.hide();
81 return true;
82 }
83 return false;
84 }
85
Jian Lia54de5a2016-01-20 23:10:39 -080086 function addCloseBtn(div) {
87 is.loadEmbeddedIcon(div, 'plus', 30);
88 div.select('g').attr('transform', 'translate(25, 0) rotate(45)');
89 div.on('click', closePanel);
90 }
91
92 function setUpPanel() {
93 var container, closeBtn, tblDiv;
94 detailsPanel.empty();
95
96 container = detailsPanel.append('div').classed('container', true);
97
98 top = container.append('div').classed('top', true);
99 closeBtn = top.append('div').classed('close-btn', true);
100 addCloseBtn(closeBtn);
101 iconDiv = top.append('div').classed('dev-icon', true);
Jian Lia54de5a2016-01-20 23:10:39 -0800102
103 tblDiv = top.append('div').classed('top-tables', true);
104 tblDiv.append('div').classed('left', true).append('table');
105 tblDiv.append('div').classed('right', true).append('table');
Jian Lida253e02016-01-21 17:46:17 -0800106 tblDiv.append('div').classed('description', true).append('table');
Jian Lia54de5a2016-01-20 23:10:39 -0800107
108 top.append('hr');
109
Jian Lida253e02016-01-21 17:46:17 -0800110 middle = container.append('div').classed('middle', true);
111 tblDiv = middle.append('div').classed('middle-tables', true);
112 tblDiv.append('div').classed('readme', true).append('table');
113
114 middle.append('hr');
115
Jian Lia54de5a2016-01-20 23:10:39 -0800116 bottom = container.append('div').classed('bottom', true);
Jian Lida253e02016-01-21 17:46:17 -0800117 tblDiv = bottom.append('div').classed('bottom-tables', true).append('table');
118 tblDiv.append('h2').html('Features');
119 tblDiv.append('div').classed('features', true).append('table');
120 tblDiv.append('h2').html('Required Apps');
121 tblDiv.append('div').classed('required-apps', true).append('table');
122 tblDiv.append('h2').html('Permissions');
123 tblDiv.append('div').classed('permissions', true).append('table');
Jian Lia54de5a2016-01-20 23:10:39 -0800124 }
125
126 function addProp(tbody, index, value) {
127 var tr = tbody.append('tr');
128
129 function addCell(cls, txt) {
130 tr.append('td').attr('class', cls).html(txt);
131 }
132 addCell('label', friendlyProps[index] + ' :');
133 addCell('value', value);
134 }
135
Simon Hunta477b602016-01-22 12:10:07 -0800136 function addUrl(tbody, index, value) {
137 var href = '<a href="' + value + '" target="_blank">' + value + '</a>';
138 addProp(tbody, index, href);
139 }
140
Jian Lia54de5a2016-01-20 23:10:39 -0800141 function addIcon(tbody, value) {
142 var tr = tbody.append('tr');
143 var td = tr.append('td');
144 td.append('img').attr('src', iconUrlPrefix + value + iconUrlSuffix);
145 }
146
Jian Lida253e02016-01-21 17:46:17 -0800147 function addContent(tbody, value) {
Jian Lia54de5a2016-01-20 23:10:39 -0800148 var tr = tbody.append('tr');
149 tr.append('td').html(value);
150 }
151
152 function populateTop(tblDiv, details) {
153 var leftTbl = tblDiv.select('.left')
154 .select('table')
155 .append('tbody'),
156 rightTbl = tblDiv.select('.right')
157 .select('table')
158 .append('tbody'),
Jian Lida253e02016-01-21 17:46:17 -0800159 descriptionTbl = tblDiv.select('.description')
Jian Lia54de5a2016-01-20 23:10:39 -0800160 .select('table')
161 .append('tbody');
162
163 top.select('h2').html(details.name);
164
165 // place application icon to the left table
166 addIcon(leftTbl, details.id);
167
168 // place rest of the fields to the right table
169 propOrder.forEach(function (prop, i) {
Simon Hunta477b602016-01-22 12:10:07 -0800170 var fn = prop === 'url' ? addUrl : addProp;
171 fn(rightTbl, i, details[prop]);
Jian Lia54de5a2016-01-20 23:10:39 -0800172 });
173
Jian Lida253e02016-01-21 17:46:17 -0800174 // place description field to the description table
175 addContent(descriptionTbl, details.desc);
176 }
177
178 function populateMiddle(tblDiv, details) {
179 var readmeTbl = tblDiv.select('.readme')
180 .select('table')
181 .append('tbody');
182
Jian Lia54de5a2016-01-20 23:10:39 -0800183 // place readme field to the readme table
Jian Lida253e02016-01-21 17:46:17 -0800184 addContent(readmeTbl, details.readme);
Jian Lia54de5a2016-01-20 23:10:39 -0800185 }
186
187 function populateName(div, name) {
188 var lab = div.select('.label'),
189 val = div.select('.value');
190 lab.html('Friendly Name:');
191 val.html(name);
192 }
193
194 function populateDetails(details) {
Jian Lida253e02016-01-21 17:46:17 -0800195 var nameDiv, topTbs, middleTbs, bottomTbs;
Jian Lia54de5a2016-01-20 23:10:39 -0800196 setUpPanel();
197
198 nameDiv = top.select('.name-div');
199 topTbs = top.select('.top-tables');
Jian Lida253e02016-01-21 17:46:17 -0800200 middleTbs = middle.select('.middle-tables');
201 bottomTbs = bottom.select('.bottom-tables');
Jian Lia54de5a2016-01-20 23:10:39 -0800202
203 populateName(nameDiv, details.name);
204 populateTop(topTbs, details);
Jian Lida253e02016-01-21 17:46:17 -0800205 populateMiddle(middleTbs, details);
206 populateBottom(bottomTbs, details);
Jian Lia54de5a2016-01-20 23:10:39 -0800207
208 detailsPanel.height(pHeight);
209 }
210
Jian Lida253e02016-01-21 17:46:17 -0800211 function addItem(tbody, item) {
212 var tr = tbody.append('tr').attr('width', tbWidth + 'px');
213 tr.append('td').attr('width', tbWidth + 'px')
214 .attr('style', 'text-align:left').html(item);
215 }
Jian Lia54de5a2016-01-20 23:10:39 -0800216
Jian Lida253e02016-01-21 17:46:17 -0800217 function addItems(table, items) {
218 var tbody = table.append('tbody');
219 items.forEach(function (item) {
220 addItem(tbody, item);
221 });
222 }
223
224 function populateBottom(tblDiv, details) {
225 var featuresTbl = tblDiv.select('.features')
226 .select('table'),
227 permissionsTbl = tblDiv.select('.permissions')
228 .select('table'),
229 requiredAppsTbl = tblDiv.select('.required-apps')
230 .select('table');
231
232 addItems(featuresTbl, details.features);
233 addItems(requiredAppsTbl, details._required_apps);
234 addItems(permissionsTbl, details.permissions);
235
236 featuresTbl.style({
Jian Lia54de5a2016-01-20 23:10:39 -0800237 width: tbWidth + 'px',
238 overflow: 'auto',
239 display: 'block'
240 });
241
Jian Lida253e02016-01-21 17:46:17 -0800242 detailsPanel.width(tbWidth + ctnrPdg);
Jian Lia54de5a2016-01-20 23:10:39 -0800243 }
244
245 function respDetailsCb(data) {
246 $scope.panelData = data.details;
247 $scope.$apply();
248 }
Bri Prebilic Coleb699a162015-04-13 12:01:39 -0700249
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700250 angular.module('ovApp', [])
251 .controller('OvAppCtrl',
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700252 ['$log', '$scope', '$http',
Jian Lia54de5a2016-01-20 23:10:39 -0800253 'FnService', 'TableBuilderService', 'PanelService', 'WebSocketService',
254 'IconService', 'UrlFnService', 'KeyService', 'DialogService',
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700255
Jian Lia54de5a2016-01-20 23:10:39 -0800256 function (_$log_, _$scope_, $http, _fs_, tbs, _ps_, _wss_, _is_, ufs, _ks_, ds) {
257 $log = _$log_;
258 $scope = _$scope_;
259 wss = _wss_;
260 ks = _ks_;
261 fs = _fs_;
262 ps = _ps_;
263 is = _is_;
264 $scope.panelData = {};
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700265 $scope.ctrlBtnState = {};
Bri Prebilic Cole6c82ade2015-08-05 11:12:30 -0700266 $scope.uploadTip = 'Upload an application (.oar file)';
Bri Prebilic Coleeef67ae2015-07-01 16:26:59 -0700267 $scope.activateTip = 'Activate selected application';
268 $scope.deactivateTip = 'Deactivate selected application';
269 $scope.uninstallTip = 'Uninstall selected application';
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700270
Jian Lia54de5a2016-01-20 23:10:39 -0800271 var handlers = {};
272
273 // details panel handlers
274 handlers[detailsResp] = respDetailsCb;
275 wss.bindHandlers(handlers);
276
Bri Prebilic Coleb699a162015-04-13 12:01:39 -0700277 function selCb($event, row) {
Simon Hunt3f92c432016-01-12 17:34:23 -0800278 // $scope.selId is set by code in tableBuilder
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700279 $scope.ctrlBtnState.selection = !!$scope.selId;
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700280 refreshCtrls();
Simon Hunt3f92c432016-01-12 17:34:23 -0800281 ds.closeDialog(); // don't want dialog from previous selection
Jian Lia54de5a2016-01-20 23:10:39 -0800282
283 if ($scope.selId) {
284 wss.sendEvent(detailsReq, { id: row.id });
285 } else {
286 $scope.hidePanel();
287 }
288 $log.debug('Got a click on:', row);
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700289 }
290
Bri Prebilic Colea7f81e52015-06-23 10:11:08 -0700291 function refreshCtrls() {
292 var row, rowIdx;
293 if ($scope.ctrlBtnState.selection) {
294 rowIdx = fs.find($scope.selId, $scope.tableData);
295 row = rowIdx >= 0 ? $scope.tableData[rowIdx] : null;
296
297 $scope.ctrlBtnState.installed = row && row.state === INSTALLED;
298 $scope.ctrlBtnState.active = row && row.state === ACTIVE;
299 } else {
300 $scope.ctrlBtnState.installed = false;
301 $scope.ctrlBtnState.active = false;
302 }
Thomas Vachuska619c5382015-04-02 13:41:47 -0700303 }
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700304
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700305 tbs.buildTable({
306 scope: $scope,
307 tag: 'app',
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700308 selCb: selCb,
Simon Hunta678b842016-01-11 17:14:18 -0800309 respCb: refreshCtrls,
310 // pre-populate sort so active apps are at the top of the list
311 sortParams: {
Simon Hunt051e9fa2016-01-19 15:54:22 -0800312 firstCol: 'state',
313 firstDir: 'desc',
314 secondCol: 'id',
315 secondDir: 'asc'
Simon Hunta678b842016-01-11 17:14:18 -0800316 }
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700317 });
318
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700319 // TODO: reexamine where keybindings should be - directive or controller?
320 ks.keyBindings({
321 esc: [$scope.selectCallback, 'Deselect app'],
322 _helpFormat: ['esc']
323 });
324 ks.gestureNotes([
325 ['click row', 'Select / deselect app'],
326 ['scroll down', 'See more apps']
327 ]);
328
Simon Hunt3f92c432016-01-12 17:34:23 -0800329 function createConfirmationText(action, itemId) {
Simon Hunt8d28a552016-01-11 14:01:02 -0800330 var content = ds.createDiv();
Simon Hunt3f92c432016-01-12 17:34:23 -0800331 content.append('p').text(action + ' ' + itemId);
332 if (strongWarning[itemId]) {
333 content.append('p').text(discouragement).classed('strong', true);
334 }
Simon Hunt8d28a552016-01-11 14:01:02 -0800335 return content;
336 }
337
338 function confirmAction(action) {
Simon Hunt3f92c432016-01-12 17:34:23 -0800339 var itemId = $scope.selId,
Simon Hunt8d28a552016-01-11 14:01:02 -0800340 spar = $scope.sortParams;
341
342 function dOk() {
Simon Hunt3f92c432016-01-12 17:34:23 -0800343 $log.debug('Initiating', action, 'of', itemId);
Simon Hunt8d28a552016-01-11 14:01:02 -0800344 wss.sendEvent(appMgmtReq, {
345 action: action,
Simon Hunt3f92c432016-01-12 17:34:23 -0800346 name: itemId,
Simon Hunt8d28a552016-01-11 14:01:02 -0800347 sortCol: spar.sortCol,
348 sortDir: spar.sortDir
349 });
350 }
351
352 function dCancel() {
Simon Hunt3f92c432016-01-12 17:34:23 -0800353 $log.debug('Canceling', action, 'of', itemId);
Simon Hunt8d28a552016-01-11 14:01:02 -0800354 }
355
356 ds.openDialog(dialogId, dialogOpts)
357 .setTitle('Confirm Action')
Simon Hunt3f92c432016-01-12 17:34:23 -0800358 .addContent(createConfirmationText(action, itemId))
Simon Hunt8d28a552016-01-11 14:01:02 -0800359 .addButton('OK', dOk)
360 .addButton('Cancel', dCancel);
361 }
362
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700363 $scope.appAction = function (action) {
364 if ($scope.ctrlBtnState.selection) {
Simon Hunt8d28a552016-01-11 14:01:02 -0800365 confirmAction(action);
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700366 }
Bri Prebilic Colebd0bc772015-05-13 13:02:26 -0700367 };
Thomas Vachuska530e52a2015-05-06 19:51:32 -0700368
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700369 $scope.$on('FileChanged', function () {
370 var formData = new FormData();
371 if ($scope.appFile) {
372 formData.append('file', $scope.appFile);
Simon Hunt8d28a552016-01-11 14:01:02 -0800373 $http.post(ufs.rsUrl(fileUploadUrl), formData, {
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700374 transformRequest: angular.identity,
375 headers: {
376 'Content-Type': undefined
377 }
378 })
Bri Prebilic Colea7f81e52015-06-23 10:11:08 -0700379 .finally(function () {
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700380 $scope.sortCallback($scope.sortParams);
381 document.getElementById('inputFileForm').reset();
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700382 });
Thomas Vachuskaa7a0f562015-04-14 23:27:44 -0700383 }
Thomas Vachuskaa7a0f562015-04-14 23:27:44 -0700384 });
385
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700386 $scope.$on('$destroy', function () {
387 ks.unbindKeys();
Jian Lia54de5a2016-01-20 23:10:39 -0800388 wss.unbindHandlers(handlers);
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700389 });
390
Thomas Vachuska619c5382015-04-02 13:41:47 -0700391 $log.log('OvAppCtrl has been created');
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700392 }])
393
394 // triggers the input form to appear when button is clicked
395 .directive('triggerForm', function () {
396 return {
397 restrict: 'A',
398 link: function (scope, elem) {
399 elem.bind('click', function () {
400 document.getElementById('uploadFile')
Bri Prebilic Cole6c82ade2015-08-05 11:12:30 -0700401 .dispatchEvent(new MouseEvent('click'));
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700402 });
403 }
404 };
405 })
406
407 // binds the model file to the scope in scope.appFile
408 // sends upload request to the server
409 .directive('fileModel', ['$parse',
410 function ($parse) {
411 return {
412 restrict: 'A',
413 link: function (scope, elem, attrs) {
414 var model = $parse(attrs.fileModel),
415 modelSetter = model.assign;
416
417 elem.bind('change', function () {
418 scope.$apply(function () {
419 modelSetter(scope, elem[0].files[0]);
420 });
421 scope.$emit('FileChanged');
422 });
423 }
424 };
Jian Lia54de5a2016-01-20 23:10:39 -0800425 }])
426
427 .directive('applicationDetailsPanel',
428 ['$rootScope', '$window', '$timeout', 'KeyService',
429 function ($rootScope, $window, $timeout, ks) {
430 return function (scope) {
431 var unbindWatch;
432
433 function heightCalc() {
434 pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
435 + topPdg;
436 wSize = fs.windowSize(pStartY);
437 pHeight = wSize.height;
438 }
439
440 function initPanel() {
441 heightCalc();
442 createDetailsPane();
443 $log.debug('start to initialize panel!');
444 }
445
446 // Safari has a bug where it renders the fixed-layout table wrong
447 // if you ask for the window's size too early
448 if (scope.onos.browser === 'safari') {
449 $timeout(initPanel);
450 } else {
451 initPanel();
452 }
453 // create key bindings to handle panel
454 ks.keyBindings({
Simon Hunta477b602016-01-22 12:10:07 -0800455 esc: [closePanel, 'Close the details panel'],
Jian Lia54de5a2016-01-20 23:10:39 -0800456 _helpFormat: ['esc']
457 });
458 ks.gestureNotes([
459 ['click', 'Select a row to show application details'],
460 ['scroll down', 'See more application']
461 ]);
462
463 // if the panelData changes
464 scope.$watch('panelData', function () {
465 if (!fs.isEmptyObject(scope.panelData)) {
466 populateDetails(scope.panelData);
467 detailsPanel.show();
468 }
469 });
470
471 // if the window size changes
472 unbindWatch = $rootScope.$watchCollection(
473 function () {
474 return {
475 h: $window.innerHeight,
476 w: $window.innerWidth
477 };
478 }, function () {
479 if (!fs.isEmptyObject(scope.panelData)) {
480 heightCalc();
481 populateDetails(scope.panelData);
482 }
483 }
484 );
485
486 scope.$on('$destroy', function () {
487 unbindWatch();
488 ks.unbindKeys();
489 ps.destroyPanel(pName);
490 });
491 };
492 }]);
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700493}());