blob: c933053c2d52981cf63a6c17e13e40a4a2a20107 [file] [log] [blame]
Sean Condon83fc39f2018-04-19 18:56:13 +01001/*
Sean Condon2aa86092018-07-16 09:04:05 +01002 * Copyright 2018-present Open Networking Foundation
Sean Condon83fc39f2018-04-19 18:56:13 +01003 *
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 */
Sean Condona00bf382018-06-23 07:54:01 +010016import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
Sean Condon2aa86092018-07-16 09:04:05 +010017import { HttpClient, HttpErrorResponse } from '@angular/common/http';
18
Sean Condon5ca00262018-09-06 17:55:25 +010019import {
20 FnService,
21 IconService,
22 LionService,
23 LoadingService,
24 LogService,
25 TableBaseImpl, TableResponse, TableFilter, SortParams, SortDir,
26 UrlFnService,
27 WebSocketService,
28 TableFilterPipe
29} from 'gui2-fw-lib';
Sean Condon83fc39f2018-04-19 18:56:13 +010030
Sean Condon2bd11b72018-06-15 08:00:48 +010031const INSTALLED = 'INSTALLED';
32const ACTIVE = 'ACTIVE';
Sean Condon2aa86092018-07-16 09:04:05 +010033const APPMGMTREQ = 'appManagementRequest';
34const DETAILSREQ = 'appDetailsRequest';
35const FILEUPLOADURL = 'upload';
36const FILEDOWNLOADURL = 'download';
37const ACTIVATEOPTION = '?activate=true';
38const DRAGDROPMSG1 = 'Drag and drop one file at a time';
39const DRAGDROPMSGEXT = 'Only files ending in .oar can be dropped';
Sean Condon28ecc5f2018-06-25 12:50:16 +010040
41/** Prefix to access the REST service for applications */
Sean Condon3c8e5582018-08-28 23:22:43 +010042export const APPURLPREFIX = 'rs/applications/';
Sean Condon28ecc5f2018-06-25 12:50:16 +010043/** Suffix to access the icon of the application - gives back an image */
44export const ICONURLSUFFIX = '/icon';
45
Sean Condon2bd11b72018-06-15 08:00:48 +010046const downloadSuffix = '/download';
47const dialogId = 'app-dialog';
48const dialogOpts = {
49 edge: 'right',
50 width: 400,
51};
52const strongWarning = {
53 'org.onosproject.drivers': true,
54};
55const propOrder = ['id', 'state', 'category', 'version', 'origin', 'role'];
56
Sean Condon28ecc5f2018-06-25 12:50:16 +010057/**
58 * Model of the data returned through the Web Socket about apps.
59 */
Sean Condon2bd11b72018-06-15 08:00:48 +010060interface AppTableResponse extends TableResponse {
Sean Condon28ecc5f2018-06-25 12:50:16 +010061 apps: App[];
Sean Condon2bd11b72018-06-15 08:00:48 +010062}
63
Sean Condon28ecc5f2018-06-25 12:50:16 +010064/**
65 * Model of the data returned through Web Socket for a single App
66 */
67export interface App {
Sean Condon2bd11b72018-06-15 08:00:48 +010068 category: string;
69 desc: string;
Sean Condon28ecc5f2018-06-25 12:50:16 +010070 features: string[];
Sean Condon2bd11b72018-06-15 08:00:48 +010071 icon: string;
72 id: string;
73 origin: string;
Sean Condon28ecc5f2018-06-25 12:50:16 +010074 permissions: string[];
Sean Condon2bd11b72018-06-15 08:00:48 +010075 readme: string;
Sean Condon28ecc5f2018-06-25 12:50:16 +010076 required_apps: string[];
Sean Condon2bd11b72018-06-15 08:00:48 +010077 role: string;
78 state: string;
79 title: string;
80 url: string;
81 version: string;
82 _iconid_state: string;
83}
84
Sean Condon2aa86092018-07-16 09:04:05 +010085export enum AppAction {
86 NONE = 0,
87 ACTIVATE = 1,
88 DEACTIVATE = 2,
89 UNINSTALL = 3,
90}
91
Sean Condon28ecc5f2018-06-25 12:50:16 +010092/**
93 * Model of the Control Button
94 */
Sean Condon2bd11b72018-06-15 08:00:48 +010095interface CtrlBtnState {
96 installed: boolean;
97 selection: string;
98 active: boolean;
99}
100
Sean Condon83fc39f2018-04-19 18:56:13 +0100101/**
Sean Condon2aa86092018-07-16 09:04:05 +0100102 * ONOS GUI -- Apps View Component extends TableBaseImpl
Sean Condon83fc39f2018-04-19 18:56:13 +0100103 */
104@Component({
105 selector: 'onos-apps',
106 templateUrl: './apps.component.html',
Sean Condon2bd11b72018-06-15 08:00:48 +0100107 styleUrls: [
108 './apps.component.css', './apps.theme.css',
Sean Condon28ecc5f2018-06-25 12:50:16 +0100109 '../../../fw/widget/table.css', '../../../fw/widget/table.theme.css'
Sean Condon2bd11b72018-06-15 08:00:48 +0100110 ]
Sean Condon83fc39f2018-04-19 18:56:13 +0100111})
Sean Condon2bd11b72018-06-15 08:00:48 +0100112export class AppsComponent extends TableBaseImpl implements OnInit, OnDestroy {
113
114 // deferred localization strings
115 lionFn; // Function
116 warnDeactivate: string;
117 warnOwnRisk: string;
Sean Condon2bd11b72018-06-15 08:00:48 +0100118 ctrlBtnState: CtrlBtnState;
Sean Condona00bf382018-06-23 07:54:01 +0100119 appFile: any;
Sean Condona00bf382018-06-23 07:54:01 +0100120
121 uploadTip: string;
122 activateTip: string;
123 deactivateTip: string;
124 uninstallTip: string;
125 downloadTip: string;
Sean Condon2aa86092018-07-16 09:04:05 +0100126 alertMsg: string;
127 AppActionEnum: any = AppAction;
128 appAction: AppAction = AppAction.NONE;
129 confirmMsg: string = '';
130 strongWarning: string = '';
Sean Condon83fc39f2018-04-19 18:56:13 +0100131
132 constructor(
Sean Condon2bd11b72018-06-15 08:00:48 +0100133 protected fs: FnService,
Sean Condon83fc39f2018-04-19 18:56:13 +0100134 private is: IconService,
Sean Condon2bd11b72018-06-15 08:00:48 +0100135 private lion: LionService,
136 protected ls: LoadingService,
137 protected log: LogService,
Sean Condon83fc39f2018-04-19 18:56:13 +0100138 private ufs: UrlFnService,
Sean Condon2bd11b72018-06-15 08:00:48 +0100139 protected wss: WebSocketService,
Sean Condona00bf382018-06-23 07:54:01 +0100140 @Inject('Window') private window: Window,
Sean Condon2aa86092018-07-16 09:04:05 +0100141 private httpClient: HttpClient
Sean Condon83fc39f2018-04-19 18:56:13 +0100142 ) {
Sean Condon2bd11b72018-06-15 08:00:48 +0100143 super(fs, null, log, wss, 'app');
144 this.responseCallback = this.appResponseCb;
Sean Condon2aa86092018-07-16 09:04:05 +0100145 this.parentSelCb = this.rowSelection;
Sean Condon28ecc5f2018-06-25 12:50:16 +0100146 // pre-populate sort so active apps are at the top of the list
Sean Condon2bd11b72018-06-15 08:00:48 +0100147 this.sortParams = {
148 firstCol: 'state',
Sean Condon28ecc5f2018-06-25 12:50:16 +0100149 firstDir: SortDir.desc,
Sean Condon2bd11b72018-06-15 08:00:48 +0100150 secondCol: 'title',
Sean Condon28ecc5f2018-06-25 12:50:16 +0100151 secondDir: SortDir.asc,
Sean Condon2bd11b72018-06-15 08:00:48 +0100152 };
Sean Condon28ecc5f2018-06-25 12:50:16 +0100153 // We want doLion() to be called only after the Lion
154 // service is populated (from the WebSocket)
155 // If lion is not ready we make do with a dummy function
156 // As soon a lion gets loaded this function will be replaced with
157 // the real thing
158 if (this.lion.ubercache.length === 0) {
159 this.lionFn = this.dummyLion;
160 this.lion.loadCbs.set('apps', () => this.doLion());
161 } else {
162 this.doLion();
163 }
164
Sean Condon2bd11b72018-06-15 08:00:48 +0100165 this.ctrlBtnState = <CtrlBtnState>{
166 installed: false,
167 active: false
168 };
Sean Condon83fc39f2018-04-19 18:56:13 +0100169 }
170
Sean Condon28ecc5f2018-06-25 12:50:16 +0100171 /**
172 * Initialize querying the WebSocket for App table details
173 */
Sean Condon83fc39f2018-04-19 18:56:13 +0100174 ngOnInit() {
Sean Condon2bd11b72018-06-15 08:00:48 +0100175 this.init();
Sean Condon83fc39f2018-04-19 18:56:13 +0100176 }
177
Sean Condon28ecc5f2018-06-25 12:50:16 +0100178 /**
179 * Stop sending queries to WebSocket
180 */
Sean Condon2bd11b72018-06-15 08:00:48 +0100181 ngOnDestroy() {
Sean Condon28ecc5f2018-06-25 12:50:16 +0100182 this.lion.loadCbs.delete('apps');
Sean Condon2bd11b72018-06-15 08:00:48 +0100183 this.destroy();
184 this.log.debug('AppComponent destroyed');
185 }
186
187 /**
188 * The callback called when App data returns from WSS
189 */
190 appResponseCb(data: AppTableResponse) {
191 this.log.debug('App response received for ', data.apps.length, 'apps');
192 }
193
Sean Condon2aa86092018-07-16 09:04:05 +0100194 /**
195 * called when a row is selected - sets the state of control icons
196 */
197 rowSelection(event: any, selRow: any) {
198 this.ctrlBtnState.installed = this.selId && selRow && selRow.state === INSTALLED;
199 this.ctrlBtnState.active = this.selId && selRow && selRow.state === ACTIVE;
200 this.ctrlBtnState.selection = this.selId;
201 this.log.debug('Row ', this.selId, 'selected', this.ctrlBtnState);
202 }
Sean Condon2bd11b72018-06-15 08:00:48 +0100203
Sean Condon2aa86092018-07-16 09:04:05 +0100204
205 /**
206 * Perform one of the app actions - activate, deactivate or uninstall
207 * Raises a dialog which calls back the dOk() below
208 */
209 confirmAction(action: AppAction): void {
210 this.appAction = action;
211 const appActionLc = (<string>AppAction[this.appAction]).toLowerCase();
212
213 this.confirmMsg = this.lionFn(appActionLc) + ' ' + this.selId;
214 if (strongWarning[this.selId]) {
215 this.strongWarning = this.warnDeactivate + '\n' + this.warnOwnRisk;
Sean Condon2bd11b72018-06-15 08:00:48 +0100216 }
Sean Condon2aa86092018-07-16 09:04:05 +0100217
218 this.log.debug('Initiating', this.appAction, 'of', this.selId);
Sean Condon2bd11b72018-06-15 08:00:48 +0100219 }
220
Sean Condon2aa86092018-07-16 09:04:05 +0100221 /**
222 * Callback when the Confirm dialog is shown and a choice is made
223 */
224 dOk(choice: boolean) {
225 const appActionLc = (<string>AppAction[this.appAction]).toLowerCase();
226 if (choice) {
227 this.log.debug('Confirmed', appActionLc, 'on', this.selId);
Sean Condon2bd11b72018-06-15 08:00:48 +0100228
Sean Condon2aa86092018-07-16 09:04:05 +0100229 this.wss.sendEvent(APPMGMTREQ, {
230 action: appActionLc,
231 name: this.selId,
232 sortCol: this.sortParams.firstCol,
233 sortDir: SortDir[this.sortParams.firstDir],
Sean Condon2bd11b72018-06-15 08:00:48 +0100234 });
Sean Condon2aa86092018-07-16 09:04:05 +0100235 if (this.appAction === AppAction.UNINSTALL) {
236 this.selId = '';
Sean Condon2bd11b72018-06-15 08:00:48 +0100237 } else {
Sean Condon2aa86092018-07-16 09:04:05 +0100238 this.wss.sendEvent(DETAILSREQ, { id: this.selId });
Sean Condon2bd11b72018-06-15 08:00:48 +0100239 }
Sean Condon2bd11b72018-06-15 08:00:48 +0100240
Sean Condon2aa86092018-07-16 09:04:05 +0100241 } else {
242 this.log.debug('Cancelled', appActionLc, 'on', this.selId);
Sean Condon2bd11b72018-06-15 08:00:48 +0100243 }
Sean Condon2aa86092018-07-16 09:04:05 +0100244 this.confirmMsg = '';
245 this.strongWarning = '';
Sean Condon2bd11b72018-06-15 08:00:48 +0100246 }
247
248 downloadApp() {
249 if (this.ctrlBtnState.selection) {
Sean Condon2aa86092018-07-16 09:04:05 +0100250 (<any>this.window).location = APPURLPREFIX + this.selId + '/' + FILEDOWNLOADURL;
Sean Condon2bd11b72018-06-15 08:00:48 +0100251 }
252 }
253
254 /**
Sean Condon2aa86092018-07-16 09:04:05 +0100255 * When the file is selected this fires
256 * It passes the file on to the server through a POST request
257 * If there is an error its logged and raised to the user through Flash Component
258 */
259 fileEvent(event: any, activateImmediately?: boolean) {
260 this.log.debug('File event for', event.target.files[0]);
261 const formData = new FormData();
262 formData.append('file', event.target.files[0]);
263 let url = APPURLPREFIX + FILEUPLOADURL;
264 if (activateImmediately) {
265 url += ACTIVATEOPTION;
266 }
267 this.httpClient
268 .post<any>(APPURLPREFIX + FILEUPLOADURL, formData)
269 .subscribe(
270 data => this.log.debug(data),
271 err => {
272 this.log.warn(err.message);
273 this.alertMsg = err.message; // This will activate flash msg
274 }
275 );
276
277 }
278
279 /**
280 * When the upload button is clicked pass this on to the file input (hidden)
281 */
282 triggerForm() {
283 document.getElementById('uploadFile')
284 .dispatchEvent(new MouseEvent('click'));
285 }
286
287 /**
Sean Condon28ecc5f2018-06-25 12:50:16 +0100288 * Read the LION bundle for App and set up the lionFn
Sean Condon2bd11b72018-06-15 08:00:48 +0100289 */
290 doLion() {
291 this.lionFn = this.lion.bundle('core.view.App');
292
293 this.warnDeactivate = this.lionFn('dlg_warn_deactivate');
294 this.warnOwnRisk = this.lionFn('dlg_warn_own_risk');
295
Sean Condon28ecc5f2018-06-25 12:50:16 +0100296 this.uploadTip = this.lionFn('tt_ctl_upload');
297 this.activateTip = this.lionFn('tt_ctl_activate');
298 this.deactivateTip = this.lionFn('tt_ctl_deactivate');
299 this.uninstallTip = this.lionFn('tt_ctl_uninstall');
300 this.downloadTip = this.lionFn('tt_ctl_download');
Sean Condon2bd11b72018-06-15 08:00:48 +0100301 }
Sean Condona00bf382018-06-23 07:54:01 +0100302
Sean Condon2aa86092018-07-16 09:04:05 +0100303 onDrop(event: DragEvent) {
304 event.preventDefault();
305 event.stopPropagation();
306
307 const dt = event.dataTransfer;
308 const droppedFiles = dt.files;
309
310 this.log.debug(droppedFiles.length, 'File(s) dropped');
311 if (droppedFiles.length !== 1) {
312 this.log.error(DRAGDROPMSG1, droppedFiles.length, 'were dropped');
313 this.alertMsg = DRAGDROPMSG1;
314 return;
315 } else if (droppedFiles[0].name.slice(droppedFiles[0].name.length - 4) !== '.oar') {
316 this.log.error(DRAGDROPMSGEXT, droppedFiles[0].name, 'rejected');
317 this.alertMsg = DRAGDROPMSGEXT;
318 return;
319 }
320
321 const fileEvent = {
322 target: {
323 files: droppedFiles
324 }
325 };
326 this.fileEvent(fileEvent, true);
327 }
328
329 onDragOver(evt) {
330 evt.preventDefault();
331 evt.stopPropagation();
332 }
333
334 onDragLeave(evt) {
335 evt.preventDefault();
336 evt.stopPropagation();
Sean Condona00bf382018-06-23 07:54:01 +0100337 }
Bhavesh72ead492018-07-19 16:29:18 +0530338
339 deselectRow(event) {
340 this.log.debug('Details panel close event');
341 this.selId = event;
342 this.ctrlBtnState = <CtrlBtnState>{
343 installed: undefined,
344 active: undefined
345 };
346 }
Sean Condon83fc39f2018-04-19 18:56:13 +0100347}