blob: 1f1078b94f7c5dc7474fc0baf1ab5a74b7dd78d4 [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
Simon Hunt31642932016-01-22 20:34:00 -080025 var $log, $scope, wss, fs, ks, ps, is;
Jian Lia54de5a2016-01-20 23:10:39 -080026
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,
Jian Lia54de5a2016-01-20 23:10:39 -080034 wSize = false;
35
Bri Prebilic Cole522e7562015-06-22 15:56:25 -070036 // constants
37 var INSTALLED = 'INSTALLED',
38 ACTIVE = 'ACTIVE',
Simon Hunt8d28a552016-01-11 14:01:02 -080039 appMgmtReq = 'appManagementRequest',
Jian Lia54de5a2016-01-20 23:10:39 -080040 topPdg = 50,
Simon Hunt31642932016-01-22 20:34:00 -080041 panelWidth = 500,
Jian Lia54de5a2016-01-20 23:10:39 -080042 pName = 'application-details-panel',
43 detailsReq = 'appDetailsRequest',
44 detailsResp = 'appDetailsResponse',
Simon Hunt8d28a552016-01-11 14:01:02 -080045 fileUploadUrl = 'applications/upload',
Jian Lia54de5a2016-01-20 23:10:39 -080046 iconUrlPrefix = 'rs/applications/',
47 iconUrlSuffix = '/icon',
Simon Hunt8d28a552016-01-11 14:01:02 -080048 dialogId = 'app-dialog',
49 dialogOpts = {
50 edge: 'right'
Simon Hunt3f92c432016-01-12 17:34:23 -080051 },
52 strongWarning = {
53 'org.onosproject.drivers': true
54 },
55 discouragement = 'Deactivating or uninstalling this component can' +
Jian Lia54de5a2016-01-20 23:10:39 -080056 ' have serious negative consequences! Do so at your own risk!!',
Simon Hunt31642932016-01-22 20:34:00 -080057 propOrder = ['id', 'state', 'category', 'version', 'origin', 'role'],
58 friendlyProps = ['App ID', 'State', 'Category', 'Version', 'Origin', 'Role'];
59 // note: url is handled separately
Jian Lia54de5a2016-01-20 23:10:39 -080060
61 function createDetailsPane() {
62 detailsPanel = ps.createPanel(pName, {
63 width: wSize.width,
64 margin: 0,
65 hideMargin: 0
66 });
67 detailsPanel.el().style({
68 position: 'absolute',
69 top: pStartY + 'px'
70 });
71 $scope.hidePanel = function () { detailsPanel.hide(); };
72 detailsPanel.hide();
73 }
74
75 function closePanel() {
76 if (detailsPanel.isVisible()) {
77 $scope.selId = null;
78 detailsPanel.hide();
79 return true;
80 }
81 return false;
82 }
83
Jian Lia54de5a2016-01-20 23:10:39 -080084 function addCloseBtn(div) {
85 is.loadEmbeddedIcon(div, 'plus', 30);
86 div.select('g').attr('transform', 'translate(25, 0) rotate(45)');
87 div.on('click', closePanel);
88 }
89
90 function setUpPanel() {
Simon Hunt31642932016-01-22 20:34:00 -080091 var container, closeBtn, div;
92
Jian Lia54de5a2016-01-20 23:10:39 -080093 detailsPanel.empty();
Simon Hunt31642932016-01-22 20:34:00 -080094 detailsPanel.width(panelWidth);
Jian Lia54de5a2016-01-20 23:10:39 -080095
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);
Jian Lia54de5a2016-01-20 23:10:39 -0800101
Simon Hunt31642932016-01-22 20:34:00 -0800102 div = top.append('div').classed('top-content', true);
Jian Lia54de5a2016-01-20 23:10:39 -0800103
Simon Hunt31642932016-01-22 20:34:00 -0800104 function ndiv(cls, tcls) {
105 var d = div.append('div').classed(cls, true);
106 if (tcls) {
107 d.append('table').classed(tcls, true);
108 }
109 }
110
Simon Hunt877ee982016-03-09 10:53:16 -0800111 ndiv('app-title');
Simon Hunt31642932016-01-22 20:34:00 -0800112 ndiv('left app-icon');
113 ndiv('right', 'app-props');
114 ndiv('app-url');
115
116 container.append('hr');
Jian Lia54de5a2016-01-20 23:10:39 -0800117
Jian Lida253e02016-01-21 17:46:17 -0800118 middle = container.append('div').classed('middle', true);
Simon Hunt31642932016-01-22 20:34:00 -0800119 middle.append('div').classed('app-readme', true);
Jian Lida253e02016-01-21 17:46:17 -0800120
Simon Hunt31642932016-01-22 20:34:00 -0800121 container.append('hr');
Jian Lida253e02016-01-21 17:46:17 -0800122
Simon Hunt31642932016-01-22 20:34:00 -0800123 // TODO: make bottom container scrollable
Jian Lia54de5a2016-01-20 23:10:39 -0800124 bottom = container.append('div').classed('bottom', true);
Simon Hunt31642932016-01-22 20:34:00 -0800125
126 function nTable(hdr, cls) {
127 bottom.append('h2').html(hdr);
128 bottom.append('div').classed(cls, true).append('table');
129 }
130
131 nTable('Features', 'features');
132 nTable('Required Apps', 'required-apps');
133 nTable('Permissions', 'permissions');
Jian Lia54de5a2016-01-20 23:10:39 -0800134 }
135
136 function addProp(tbody, index, value) {
Simon Hunt31642932016-01-22 20:34:00 -0800137 var tr = tbody.append('tr'),
138 vcls = index ? 'value' : 'value-bold';
Jian Lia54de5a2016-01-20 23:10:39 -0800139
140 function addCell(cls, txt) {
141 tr.append('td').attr('class', cls).html(txt);
142 }
Simon Hunt31642932016-01-22 20:34:00 -0800143
144 addCell('label', friendlyProps[index] + ':');
145 addCell(vcls, value);
Jian Lia54de5a2016-01-20 23:10:39 -0800146 }
147
Simon Hunt31642932016-01-22 20:34:00 -0800148 function urlize(u) {
149 return '<i>URL:</i> <a href="' + u + '" target="_blank">' + u + '</a>';
Simon Hunta477b602016-01-22 12:10:07 -0800150 }
151
Simon Hunt31642932016-01-22 20:34:00 -0800152 function addIcon(elem, value) {
153 elem.append('img').attr('src', iconUrlPrefix + value + iconUrlSuffix);
Jian Lia54de5a2016-01-20 23:10:39 -0800154 }
155
Simon Hunt31642932016-01-22 20:34:00 -0800156 function populateTop(details) {
157 var propsBody = top.select('.app-props').append('tbody'),
158 url = details.url;
Jian Lia54de5a2016-01-20 23:10:39 -0800159
Simon Hunt877ee982016-03-09 10:53:16 -0800160 top.select('.app-title').text(details.title);
161
Simon Hunt31642932016-01-22 20:34:00 -0800162 addIcon(top.select('.app-icon'), details.id);
Jian Lia54de5a2016-01-20 23:10:39 -0800163
Jian Lia54de5a2016-01-20 23:10:39 -0800164 propOrder.forEach(function (prop, i) {
Simon Hunt31642932016-01-22 20:34:00 -0800165 addProp(propsBody, i, details[prop]);
Jian Lia54de5a2016-01-20 23:10:39 -0800166 });
167
Simon Hunt31642932016-01-22 20:34:00 -0800168 if (url) {
169 top.select('.app-url').html(urlize(url));
170 }
Jian Lida253e02016-01-21 17:46:17 -0800171 }
172
Simon Hunt31642932016-01-22 20:34:00 -0800173 function populateMiddle(details) {
174 middle.select('.app-readme').text(details.readme);
Jian Lia54de5a2016-01-20 23:10:39 -0800175 }
176
Simon Hunt31642932016-01-22 20:34:00 -0800177 function populateBottom(details) {
178
179 function addItems(cls, items) {
180 var table = bottom.select('.' + cls).select('table'),
181 tbody = table.append('tbody');
182
183 items.forEach(function (item) {
184 tbody.append('tr').append('td').html(item);
185 });
186 }
187
188 addItems('features', details.features);
189 addItems('required-apps', details.required_apps);
190 addItems('permissions', details.permissions);
Jian Lia54de5a2016-01-20 23:10:39 -0800191 }
192
193 function populateDetails(details) {
Jian Lia54de5a2016-01-20 23:10:39 -0800194 setUpPanel();
Simon Hunt31642932016-01-22 20:34:00 -0800195 populateTop(details);
196 populateMiddle(details);
197 populateBottom(details);
Jian Lia54de5a2016-01-20 23:10:39 -0800198 detailsPanel.height(pHeight);
199 }
200
Jian Lia54de5a2016-01-20 23:10:39 -0800201 function respDetailsCb(data) {
202 $scope.panelData = data.details;
203 $scope.$apply();
204 }
Bri Prebilic Coleb699a162015-04-13 12:01:39 -0700205
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700206 angular.module('ovApp', [])
207 .controller('OvAppCtrl',
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700208 ['$log', '$scope', '$http',
Simon Hunt31642932016-01-22 20:34:00 -0800209 'WebSocketService', 'FnService', 'KeyService', 'PanelService',
210 'IconService', 'UrlFnService', 'DialogService', 'TableBuilderService',
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700211
Simon Hunt31642932016-01-22 20:34:00 -0800212 function (_$log_, _$scope_, $http, _wss_, _fs_, _ks_, _ps_, _is_, ufs, ds, tbs) {
Jian Lia54de5a2016-01-20 23:10:39 -0800213 $log = _$log_;
214 $scope = _$scope_;
215 wss = _wss_;
Jian Lia54de5a2016-01-20 23:10:39 -0800216 fs = _fs_;
Simon Hunt31642932016-01-22 20:34:00 -0800217 ks = _ks_;
Jian Lia54de5a2016-01-20 23:10:39 -0800218 ps = _ps_;
219 is = _is_;
220 $scope.panelData = {};
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700221 $scope.ctrlBtnState = {};
Bri Prebilic Cole6c82ade2015-08-05 11:12:30 -0700222 $scope.uploadTip = 'Upload an application (.oar file)';
Bri Prebilic Coleeef67ae2015-07-01 16:26:59 -0700223 $scope.activateTip = 'Activate selected application';
224 $scope.deactivateTip = 'Deactivate selected application';
225 $scope.uninstallTip = 'Uninstall selected application';
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700226
Jian Lia54de5a2016-01-20 23:10:39 -0800227 var handlers = {};
228
229 // details panel handlers
230 handlers[detailsResp] = respDetailsCb;
231 wss.bindHandlers(handlers);
232
Bri Prebilic Coleb699a162015-04-13 12:01:39 -0700233 function selCb($event, row) {
Simon Hunt3f92c432016-01-12 17:34:23 -0800234 // $scope.selId is set by code in tableBuilder
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700235 $scope.ctrlBtnState.selection = !!$scope.selId;
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700236 refreshCtrls();
Simon Hunt3f92c432016-01-12 17:34:23 -0800237 ds.closeDialog(); // don't want dialog from previous selection
Jian Lia54de5a2016-01-20 23:10:39 -0800238
239 if ($scope.selId) {
240 wss.sendEvent(detailsReq, { id: row.id });
241 } else {
242 $scope.hidePanel();
243 }
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700244 }
245
Bri Prebilic Colea7f81e52015-06-23 10:11:08 -0700246 function refreshCtrls() {
247 var row, rowIdx;
248 if ($scope.ctrlBtnState.selection) {
249 rowIdx = fs.find($scope.selId, $scope.tableData);
250 row = rowIdx >= 0 ? $scope.tableData[rowIdx] : null;
251
252 $scope.ctrlBtnState.installed = row && row.state === INSTALLED;
253 $scope.ctrlBtnState.active = row && row.state === ACTIVE;
254 } else {
255 $scope.ctrlBtnState.installed = false;
256 $scope.ctrlBtnState.active = false;
257 }
Thomas Vachuska619c5382015-04-02 13:41:47 -0700258 }
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700259
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700260 tbs.buildTable({
261 scope: $scope,
262 tag: 'app',
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700263 selCb: selCb,
Simon Hunta678b842016-01-11 17:14:18 -0800264 respCb: refreshCtrls,
265 // pre-populate sort so active apps are at the top of the list
266 sortParams: {
Simon Hunt051e9fa2016-01-19 15:54:22 -0800267 firstCol: 'state',
268 firstDir: 'desc',
Simon Hunt877ee982016-03-09 10:53:16 -0800269 secondCol: 'title',
Simon Hunt051e9fa2016-01-19 15:54:22 -0800270 secondDir: 'asc'
Simon Hunta678b842016-01-11 17:14:18 -0800271 }
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700272 });
273
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700274 // TODO: reexamine where keybindings should be - directive or controller?
275 ks.keyBindings({
Simon Hunt31642932016-01-22 20:34:00 -0800276 esc: [$scope.selectCallback, 'Deselect application'],
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700277 _helpFormat: ['esc']
278 });
279 ks.gestureNotes([
Simon Hunt31642932016-01-22 20:34:00 -0800280 ['click row', 'Select / deselect application'],
281 ['scroll down', 'See more applications']
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700282 ]);
283
Simon Hunt3f92c432016-01-12 17:34:23 -0800284 function createConfirmationText(action, itemId) {
Simon Hunt8d28a552016-01-11 14:01:02 -0800285 var content = ds.createDiv();
Simon Hunt40927332016-01-22 15:29:47 -0800286 content.append('p').text(fs.cap(action) + ' ' + itemId);
Simon Hunt3f92c432016-01-12 17:34:23 -0800287 if (strongWarning[itemId]) {
288 content.append('p').text(discouragement).classed('strong', true);
289 }
Simon Hunt8d28a552016-01-11 14:01:02 -0800290 return content;
291 }
292
293 function confirmAction(action) {
Simon Hunt3f92c432016-01-12 17:34:23 -0800294 var itemId = $scope.selId,
Simon Hunt8d28a552016-01-11 14:01:02 -0800295 spar = $scope.sortParams;
296
297 function dOk() {
Simon Hunt3f92c432016-01-12 17:34:23 -0800298 $log.debug('Initiating', action, 'of', itemId);
Simon Hunt8d28a552016-01-11 14:01:02 -0800299 wss.sendEvent(appMgmtReq, {
300 action: action,
Simon Hunt3f92c432016-01-12 17:34:23 -0800301 name: itemId,
Simon Hunt8d28a552016-01-11 14:01:02 -0800302 sortCol: spar.sortCol,
303 sortDir: spar.sortDir
304 });
305 }
306
307 function dCancel() {
Simon Hunt3f92c432016-01-12 17:34:23 -0800308 $log.debug('Canceling', action, 'of', itemId);
Simon Hunt8d28a552016-01-11 14:01:02 -0800309 }
310
311 ds.openDialog(dialogId, dialogOpts)
312 .setTitle('Confirm Action')
Simon Hunt3f92c432016-01-12 17:34:23 -0800313 .addContent(createConfirmationText(action, itemId))
Simon Hunt5198f082016-02-04 13:41:17 -0800314 .addOk(dOk)
315 .addCancel(dCancel)
316 .bindKeys();
Simon Hunt8d28a552016-01-11 14:01:02 -0800317 }
318
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700319 $scope.appAction = function (action) {
320 if ($scope.ctrlBtnState.selection) {
Simon Hunt8d28a552016-01-11 14:01:02 -0800321 confirmAction(action);
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700322 }
Bri Prebilic Colebd0bc772015-05-13 13:02:26 -0700323 };
Thomas Vachuska530e52a2015-05-06 19:51:32 -0700324
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700325 $scope.$on('FileChanged', function () {
326 var formData = new FormData();
327 if ($scope.appFile) {
328 formData.append('file', $scope.appFile);
Simon Hunt8d28a552016-01-11 14:01:02 -0800329 $http.post(ufs.rsUrl(fileUploadUrl), formData, {
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700330 transformRequest: angular.identity,
331 headers: {
332 'Content-Type': undefined
333 }
334 })
Bri Prebilic Colea7f81e52015-06-23 10:11:08 -0700335 .finally(function () {
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700336 $scope.sortCallback($scope.sortParams);
337 document.getElementById('inputFileForm').reset();
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700338 });
Thomas Vachuskaa7a0f562015-04-14 23:27:44 -0700339 }
Thomas Vachuskaa7a0f562015-04-14 23:27:44 -0700340 });
341
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700342 $scope.$on('$destroy', function () {
343 ks.unbindKeys();
Jian Lia54de5a2016-01-20 23:10:39 -0800344 wss.unbindHandlers(handlers);
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700345 });
346
Thomas Vachuska619c5382015-04-02 13:41:47 -0700347 $log.log('OvAppCtrl has been created');
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700348 }])
349
350 // triggers the input form to appear when button is clicked
351 .directive('triggerForm', function () {
352 return {
353 restrict: 'A',
354 link: function (scope, elem) {
355 elem.bind('click', function () {
356 document.getElementById('uploadFile')
Bri Prebilic Cole6c82ade2015-08-05 11:12:30 -0700357 .dispatchEvent(new MouseEvent('click'));
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700358 });
359 }
360 };
361 })
362
363 // binds the model file to the scope in scope.appFile
364 // sends upload request to the server
365 .directive('fileModel', ['$parse',
366 function ($parse) {
367 return {
368 restrict: 'A',
369 link: function (scope, elem, attrs) {
370 var model = $parse(attrs.fileModel),
371 modelSetter = model.assign;
372
373 elem.bind('change', function () {
374 scope.$apply(function () {
375 modelSetter(scope, elem[0].files[0]);
376 });
377 scope.$emit('FileChanged');
378 });
379 }
380 };
Jian Lia54de5a2016-01-20 23:10:39 -0800381 }])
382
383 .directive('applicationDetailsPanel',
384 ['$rootScope', '$window', '$timeout', 'KeyService',
385 function ($rootScope, $window, $timeout, ks) {
386 return function (scope) {
387 var unbindWatch;
388
389 function heightCalc() {
390 pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
391 + topPdg;
392 wSize = fs.windowSize(pStartY);
393 pHeight = wSize.height;
394 }
395
396 function initPanel() {
397 heightCalc();
398 createDetailsPane();
399 $log.debug('start to initialize panel!');
400 }
401
402 // Safari has a bug where it renders the fixed-layout table wrong
403 // if you ask for the window's size too early
404 if (scope.onos.browser === 'safari') {
405 $timeout(initPanel);
406 } else {
407 initPanel();
408 }
409 // create key bindings to handle panel
410 ks.keyBindings({
Simon Hunta477b602016-01-22 12:10:07 -0800411 esc: [closePanel, 'Close the details panel'],
Jian Lia54de5a2016-01-20 23:10:39 -0800412 _helpFormat: ['esc']
413 });
414 ks.gestureNotes([
415 ['click', 'Select a row to show application details'],
416 ['scroll down', 'See more application']
417 ]);
418
419 // if the panelData changes
420 scope.$watch('panelData', function () {
421 if (!fs.isEmptyObject(scope.panelData)) {
422 populateDetails(scope.panelData);
423 detailsPanel.show();
424 }
425 });
426
427 // if the window size changes
428 unbindWatch = $rootScope.$watchCollection(
429 function () {
430 return {
431 h: $window.innerHeight,
432 w: $window.innerWidth
433 };
434 }, function () {
435 if (!fs.isEmptyObject(scope.panelData)) {
436 heightCalc();
437 populateDetails(scope.panelData);
438 }
439 }
440 );
441
442 scope.$on('$destroy', function () {
443 unbindWatch();
444 ks.unbindKeys();
445 ps.destroyPanel(pName);
446 });
447 };
448 }]);
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700449}());