blob: a297bcbcfe7de59bf352a7b69cdbc77307443124 [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
111 ndiv('left app-icon');
112 ndiv('right', 'app-props');
113 ndiv('app-url');
114
115 container.append('hr');
Jian Lia54de5a2016-01-20 23:10:39 -0800116
Jian Lida253e02016-01-21 17:46:17 -0800117 middle = container.append('div').classed('middle', true);
Simon Hunt31642932016-01-22 20:34:00 -0800118 middle.append('div').classed('app-readme', true);
Jian Lida253e02016-01-21 17:46:17 -0800119
Simon Hunt31642932016-01-22 20:34:00 -0800120 container.append('hr');
Jian Lida253e02016-01-21 17:46:17 -0800121
Simon Hunt31642932016-01-22 20:34:00 -0800122 // TODO: make bottom container scrollable
Jian Lia54de5a2016-01-20 23:10:39 -0800123 bottom = container.append('div').classed('bottom', true);
Simon Hunt31642932016-01-22 20:34:00 -0800124
125 function nTable(hdr, cls) {
126 bottom.append('h2').html(hdr);
127 bottom.append('div').classed(cls, true).append('table');
128 }
129
130 nTable('Features', 'features');
131 nTable('Required Apps', 'required-apps');
132 nTable('Permissions', 'permissions');
Jian Lia54de5a2016-01-20 23:10:39 -0800133 }
134
135 function addProp(tbody, index, value) {
Simon Hunt31642932016-01-22 20:34:00 -0800136 var tr = tbody.append('tr'),
137 vcls = index ? 'value' : 'value-bold';
Jian Lia54de5a2016-01-20 23:10:39 -0800138
139 function addCell(cls, txt) {
140 tr.append('td').attr('class', cls).html(txt);
141 }
Simon Hunt31642932016-01-22 20:34:00 -0800142
143 addCell('label', friendlyProps[index] + ':');
144 addCell(vcls, value);
Jian Lia54de5a2016-01-20 23:10:39 -0800145 }
146
Simon Hunt31642932016-01-22 20:34:00 -0800147 function urlize(u) {
148 return '<i>URL:</i> <a href="' + u + '" target="_blank">' + u + '</a>';
Simon Hunta477b602016-01-22 12:10:07 -0800149 }
150
Simon Hunt31642932016-01-22 20:34:00 -0800151 function addIcon(elem, value) {
152 elem.append('img').attr('src', iconUrlPrefix + value + iconUrlSuffix);
Jian Lia54de5a2016-01-20 23:10:39 -0800153 }
154
Simon Hunt31642932016-01-22 20:34:00 -0800155 function populateTop(details) {
156 var propsBody = top.select('.app-props').append('tbody'),
157 url = details.url;
Jian Lia54de5a2016-01-20 23:10:39 -0800158
Simon Hunt31642932016-01-22 20:34:00 -0800159 addIcon(top.select('.app-icon'), details.id);
Jian Lia54de5a2016-01-20 23:10:39 -0800160
Jian Lia54de5a2016-01-20 23:10:39 -0800161 propOrder.forEach(function (prop, i) {
Simon Hunt31642932016-01-22 20:34:00 -0800162 addProp(propsBody, i, details[prop]);
Jian Lia54de5a2016-01-20 23:10:39 -0800163 });
164
Simon Hunt31642932016-01-22 20:34:00 -0800165 if (url) {
166 top.select('.app-url').html(urlize(url));
167 }
Jian Lida253e02016-01-21 17:46:17 -0800168 }
169
Simon Hunt31642932016-01-22 20:34:00 -0800170 function populateMiddle(details) {
171 middle.select('.app-readme').text(details.readme);
Jian Lia54de5a2016-01-20 23:10:39 -0800172 }
173
Simon Hunt31642932016-01-22 20:34:00 -0800174 function populateBottom(details) {
175
176 function addItems(cls, items) {
177 var table = bottom.select('.' + cls).select('table'),
178 tbody = table.append('tbody');
179
180 items.forEach(function (item) {
181 tbody.append('tr').append('td').html(item);
182 });
183 }
184
185 addItems('features', details.features);
186 addItems('required-apps', details.required_apps);
187 addItems('permissions', details.permissions);
Jian Lia54de5a2016-01-20 23:10:39 -0800188 }
189
190 function populateDetails(details) {
Jian Lia54de5a2016-01-20 23:10:39 -0800191 setUpPanel();
Simon Hunt31642932016-01-22 20:34:00 -0800192 populateTop(details);
193 populateMiddle(details);
194 populateBottom(details);
Jian Lia54de5a2016-01-20 23:10:39 -0800195 detailsPanel.height(pHeight);
196 }
197
Jian Lia54de5a2016-01-20 23:10:39 -0800198 function respDetailsCb(data) {
199 $scope.panelData = data.details;
200 $scope.$apply();
201 }
Bri Prebilic Coleb699a162015-04-13 12:01:39 -0700202
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700203 angular.module('ovApp', [])
204 .controller('OvAppCtrl',
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700205 ['$log', '$scope', '$http',
Simon Hunt31642932016-01-22 20:34:00 -0800206 'WebSocketService', 'FnService', 'KeyService', 'PanelService',
207 'IconService', 'UrlFnService', 'DialogService', 'TableBuilderService',
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700208
Simon Hunt31642932016-01-22 20:34:00 -0800209 function (_$log_, _$scope_, $http, _wss_, _fs_, _ks_, _ps_, _is_, ufs, ds, tbs) {
Jian Lia54de5a2016-01-20 23:10:39 -0800210 $log = _$log_;
211 $scope = _$scope_;
212 wss = _wss_;
Jian Lia54de5a2016-01-20 23:10:39 -0800213 fs = _fs_;
Simon Hunt31642932016-01-22 20:34:00 -0800214 ks = _ks_;
Jian Lia54de5a2016-01-20 23:10:39 -0800215 ps = _ps_;
216 is = _is_;
217 $scope.panelData = {};
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700218 $scope.ctrlBtnState = {};
Bri Prebilic Cole6c82ade2015-08-05 11:12:30 -0700219 $scope.uploadTip = 'Upload an application (.oar file)';
Bri Prebilic Coleeef67ae2015-07-01 16:26:59 -0700220 $scope.activateTip = 'Activate selected application';
221 $scope.deactivateTip = 'Deactivate selected application';
222 $scope.uninstallTip = 'Uninstall selected application';
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700223
Jian Lia54de5a2016-01-20 23:10:39 -0800224 var handlers = {};
225
226 // details panel handlers
227 handlers[detailsResp] = respDetailsCb;
228 wss.bindHandlers(handlers);
229
Bri Prebilic Coleb699a162015-04-13 12:01:39 -0700230 function selCb($event, row) {
Simon Hunt3f92c432016-01-12 17:34:23 -0800231 // $scope.selId is set by code in tableBuilder
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700232 $scope.ctrlBtnState.selection = !!$scope.selId;
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700233 refreshCtrls();
Simon Hunt3f92c432016-01-12 17:34:23 -0800234 ds.closeDialog(); // don't want dialog from previous selection
Jian Lia54de5a2016-01-20 23:10:39 -0800235
236 if ($scope.selId) {
237 wss.sendEvent(detailsReq, { id: row.id });
238 } else {
239 $scope.hidePanel();
240 }
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700241 }
242
Bri Prebilic Colea7f81e52015-06-23 10:11:08 -0700243 function refreshCtrls() {
244 var row, rowIdx;
245 if ($scope.ctrlBtnState.selection) {
246 rowIdx = fs.find($scope.selId, $scope.tableData);
247 row = rowIdx >= 0 ? $scope.tableData[rowIdx] : null;
248
249 $scope.ctrlBtnState.installed = row && row.state === INSTALLED;
250 $scope.ctrlBtnState.active = row && row.state === ACTIVE;
251 } else {
252 $scope.ctrlBtnState.installed = false;
253 $scope.ctrlBtnState.active = false;
254 }
Thomas Vachuska619c5382015-04-02 13:41:47 -0700255 }
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700256
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700257 tbs.buildTable({
258 scope: $scope,
259 tag: 'app',
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700260 selCb: selCb,
Simon Hunta678b842016-01-11 17:14:18 -0800261 respCb: refreshCtrls,
262 // pre-populate sort so active apps are at the top of the list
263 sortParams: {
Simon Hunt051e9fa2016-01-19 15:54:22 -0800264 firstCol: 'state',
265 firstDir: 'desc',
266 secondCol: 'id',
267 secondDir: 'asc'
Simon Hunta678b842016-01-11 17:14:18 -0800268 }
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -0700269 });
270
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700271 // TODO: reexamine where keybindings should be - directive or controller?
272 ks.keyBindings({
Simon Hunt31642932016-01-22 20:34:00 -0800273 esc: [$scope.selectCallback, 'Deselect application'],
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700274 _helpFormat: ['esc']
275 });
276 ks.gestureNotes([
Simon Hunt31642932016-01-22 20:34:00 -0800277 ['click row', 'Select / deselect application'],
278 ['scroll down', 'See more applications']
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700279 ]);
280
Simon Hunt3f92c432016-01-12 17:34:23 -0800281 function createConfirmationText(action, itemId) {
Simon Hunt8d28a552016-01-11 14:01:02 -0800282 var content = ds.createDiv();
Simon Hunt40927332016-01-22 15:29:47 -0800283 content.append('p').text(fs.cap(action) + ' ' + itemId);
Simon Hunt3f92c432016-01-12 17:34:23 -0800284 if (strongWarning[itemId]) {
285 content.append('p').text(discouragement).classed('strong', true);
286 }
Simon Hunt8d28a552016-01-11 14:01:02 -0800287 return content;
288 }
289
290 function confirmAction(action) {
Simon Hunt3f92c432016-01-12 17:34:23 -0800291 var itemId = $scope.selId,
Simon Hunt8d28a552016-01-11 14:01:02 -0800292 spar = $scope.sortParams;
293
294 function dOk() {
Simon Hunt3f92c432016-01-12 17:34:23 -0800295 $log.debug('Initiating', action, 'of', itemId);
Simon Hunt8d28a552016-01-11 14:01:02 -0800296 wss.sendEvent(appMgmtReq, {
297 action: action,
Simon Hunt3f92c432016-01-12 17:34:23 -0800298 name: itemId,
Simon Hunt8d28a552016-01-11 14:01:02 -0800299 sortCol: spar.sortCol,
300 sortDir: spar.sortDir
301 });
302 }
303
304 function dCancel() {
Simon Hunt3f92c432016-01-12 17:34:23 -0800305 $log.debug('Canceling', action, 'of', itemId);
Simon Hunt8d28a552016-01-11 14:01:02 -0800306 }
307
308 ds.openDialog(dialogId, dialogOpts)
309 .setTitle('Confirm Action')
Simon Hunt3f92c432016-01-12 17:34:23 -0800310 .addContent(createConfirmationText(action, itemId))
Simon Hunt5198f082016-02-04 13:41:17 -0800311 .addOk(dOk)
312 .addCancel(dCancel)
313 .bindKeys();
Simon Hunt8d28a552016-01-11 14:01:02 -0800314 }
315
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700316 $scope.appAction = function (action) {
317 if ($scope.ctrlBtnState.selection) {
Simon Hunt8d28a552016-01-11 14:01:02 -0800318 confirmAction(action);
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700319 }
Bri Prebilic Colebd0bc772015-05-13 13:02:26 -0700320 };
Thomas Vachuska530e52a2015-05-06 19:51:32 -0700321
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700322 $scope.$on('FileChanged', function () {
323 var formData = new FormData();
324 if ($scope.appFile) {
325 formData.append('file', $scope.appFile);
Simon Hunt8d28a552016-01-11 14:01:02 -0800326 $http.post(ufs.rsUrl(fileUploadUrl), formData, {
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700327 transformRequest: angular.identity,
328 headers: {
329 'Content-Type': undefined
330 }
331 })
Bri Prebilic Colea7f81e52015-06-23 10:11:08 -0700332 .finally(function () {
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700333 $scope.sortCallback($scope.sortParams);
334 document.getElementById('inputFileForm').reset();
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700335 });
Thomas Vachuskaa7a0f562015-04-14 23:27:44 -0700336 }
Thomas Vachuskaa7a0f562015-04-14 23:27:44 -0700337 });
338
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700339 $scope.$on('$destroy', function () {
340 ks.unbindKeys();
Jian Lia54de5a2016-01-20 23:10:39 -0800341 wss.unbindHandlers(handlers);
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700342 });
343
Thomas Vachuska619c5382015-04-02 13:41:47 -0700344 $log.log('OvAppCtrl has been created');
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700345 }])
346
347 // triggers the input form to appear when button is clicked
348 .directive('triggerForm', function () {
349 return {
350 restrict: 'A',
351 link: function (scope, elem) {
352 elem.bind('click', function () {
353 document.getElementById('uploadFile')
Bri Prebilic Cole6c82ade2015-08-05 11:12:30 -0700354 .dispatchEvent(new MouseEvent('click'));
Bri Prebilic Cole522e7562015-06-22 15:56:25 -0700355 });
356 }
357 };
358 })
359
360 // binds the model file to the scope in scope.appFile
361 // sends upload request to the server
362 .directive('fileModel', ['$parse',
363 function ($parse) {
364 return {
365 restrict: 'A',
366 link: function (scope, elem, attrs) {
367 var model = $parse(attrs.fileModel),
368 modelSetter = model.assign;
369
370 elem.bind('change', function () {
371 scope.$apply(function () {
372 modelSetter(scope, elem[0].files[0]);
373 });
374 scope.$emit('FileChanged');
375 });
376 }
377 };
Jian Lia54de5a2016-01-20 23:10:39 -0800378 }])
379
380 .directive('applicationDetailsPanel',
381 ['$rootScope', '$window', '$timeout', 'KeyService',
382 function ($rootScope, $window, $timeout, ks) {
383 return function (scope) {
384 var unbindWatch;
385
386 function heightCalc() {
387 pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
388 + topPdg;
389 wSize = fs.windowSize(pStartY);
390 pHeight = wSize.height;
391 }
392
393 function initPanel() {
394 heightCalc();
395 createDetailsPane();
396 $log.debug('start to initialize panel!');
397 }
398
399 // Safari has a bug where it renders the fixed-layout table wrong
400 // if you ask for the window's size too early
401 if (scope.onos.browser === 'safari') {
402 $timeout(initPanel);
403 } else {
404 initPanel();
405 }
406 // create key bindings to handle panel
407 ks.keyBindings({
Simon Hunta477b602016-01-22 12:10:07 -0800408 esc: [closePanel, 'Close the details panel'],
Jian Lia54de5a2016-01-20 23:10:39 -0800409 _helpFormat: ['esc']
410 });
411 ks.gestureNotes([
412 ['click', 'Select a row to show application details'],
413 ['scroll down', 'See more application']
414 ]);
415
416 // if the panelData changes
417 scope.$watch('panelData', function () {
418 if (!fs.isEmptyObject(scope.panelData)) {
419 populateDetails(scope.panelData);
420 detailsPanel.show();
421 }
422 });
423
424 // if the window size changes
425 unbindWatch = $rootScope.$watchCollection(
426 function () {
427 return {
428 h: $window.innerHeight,
429 w: $window.innerWidth
430 };
431 }, function () {
432 if (!fs.isEmptyObject(scope.panelData)) {
433 heightCalc();
434 populateDetails(scope.panelData);
435 }
436 }
437 );
438
439 scope.$on('$destroy', function () {
440 unbindWatch();
441 ks.unbindKeys();
442 ps.destroyPanel(pName);
443 });
444 };
445 }]);
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700446}());