blob: b2f62311fb5891738eb75b8a6183b18833e955c5 [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 Condon28ecc5f2018-06-25 12:50:16 +010019import { DialogService } from '../../../fw/layer/dialog.service';
20import { FnService } from '../../../fw/util/fn.service';
21import { IconService } from '../../../fw/svg/icon.service';
22import { KeyService } from '../../../fw/util/key.service';
23import { LionService } from '../../../fw/util/lion.service';
24import { LoadingService } from '../../../fw/layer/loading.service';
25import { LogService } from '../../../log.service';
26import { TableBaseImpl, TableResponse, TableFilter, SortParams, SortDir } from '../../../fw/widget/table.base';
27import { UrlFnService } from '../../../fw/remote/urlfn.service';
28import { WebSocketService } from '../../../fw/remote/websocket.service';
29import { TableFilterPipe } from '../../../fw/widget/tablefilter.pipe';
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 */
42export const APPURLPREFIX = '../../ui/rs/applications/'; // TODO: This is a hack to work off GUIv1 URL
43/** 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 ds: DialogService,
135 private is: IconService,
136 private ks: KeyService,
Sean Condon2bd11b72018-06-15 08:00:48 +0100137 private lion: LionService,
138 protected ls: LoadingService,
139 protected log: LogService,
Sean Condon83fc39f2018-04-19 18:56:13 +0100140 private ufs: UrlFnService,
Sean Condon2bd11b72018-06-15 08:00:48 +0100141 protected wss: WebSocketService,
Sean Condona00bf382018-06-23 07:54:01 +0100142 @Inject('Window') private window: Window,
Sean Condon2aa86092018-07-16 09:04:05 +0100143 private httpClient: HttpClient
Sean Condon83fc39f2018-04-19 18:56:13 +0100144 ) {
Sean Condon2bd11b72018-06-15 08:00:48 +0100145 super(fs, null, log, wss, 'app');
146 this.responseCallback = this.appResponseCb;
Sean Condon2aa86092018-07-16 09:04:05 +0100147 this.parentSelCb = this.rowSelection;
Sean Condon28ecc5f2018-06-25 12:50:16 +0100148 // pre-populate sort so active apps are at the top of the list
Sean Condon2bd11b72018-06-15 08:00:48 +0100149 this.sortParams = {
150 firstCol: 'state',
Sean Condon28ecc5f2018-06-25 12:50:16 +0100151 firstDir: SortDir.desc,
Sean Condon2bd11b72018-06-15 08:00:48 +0100152 secondCol: 'title',
Sean Condon28ecc5f2018-06-25 12:50:16 +0100153 secondDir: SortDir.asc,
Sean Condon2bd11b72018-06-15 08:00:48 +0100154 };
Sean Condon28ecc5f2018-06-25 12:50:16 +0100155 // We want doLion() to be called only after the Lion
156 // service is populated (from the WebSocket)
157 // If lion is not ready we make do with a dummy function
158 // As soon a lion gets loaded this function will be replaced with
159 // the real thing
160 if (this.lion.ubercache.length === 0) {
161 this.lionFn = this.dummyLion;
162 this.lion.loadCbs.set('apps', () => this.doLion());
163 } else {
164 this.doLion();
165 }
166
Sean Condon2bd11b72018-06-15 08:00:48 +0100167 this.ctrlBtnState = <CtrlBtnState>{
168 installed: false,
169 active: false
170 };
Sean Condon83fc39f2018-04-19 18:56:13 +0100171 }
172
Sean Condon28ecc5f2018-06-25 12:50:16 +0100173 /**
174 * Initialize querying the WebSocket for App table details
175 */
Sean Condon83fc39f2018-04-19 18:56:13 +0100176 ngOnInit() {
Sean Condon2bd11b72018-06-15 08:00:48 +0100177 this.init();
Sean Condon83fc39f2018-04-19 18:56:13 +0100178 }
179
Sean Condon28ecc5f2018-06-25 12:50:16 +0100180 /**
181 * Stop sending queries to WebSocket
182 */
Sean Condon2bd11b72018-06-15 08:00:48 +0100183 ngOnDestroy() {
Sean Condon28ecc5f2018-06-25 12:50:16 +0100184 this.lion.loadCbs.delete('apps');
Sean Condon2bd11b72018-06-15 08:00:48 +0100185 this.destroy();
186 this.log.debug('AppComponent destroyed');
187 }
188
189 /**
190 * The callback called when App data returns from WSS
191 */
192 appResponseCb(data: AppTableResponse) {
193 this.log.debug('App response received for ', data.apps.length, 'apps');
194 }
195
Sean Condon2aa86092018-07-16 09:04:05 +0100196 /**
197 * called when a row is selected - sets the state of control icons
198 */
199 rowSelection(event: any, selRow: any) {
200 this.ctrlBtnState.installed = this.selId && selRow && selRow.state === INSTALLED;
201 this.ctrlBtnState.active = this.selId && selRow && selRow.state === ACTIVE;
202 this.ctrlBtnState.selection = this.selId;
203 this.log.debug('Row ', this.selId, 'selected', this.ctrlBtnState);
204 }
Sean Condon2bd11b72018-06-15 08:00:48 +0100205
Sean Condon2aa86092018-07-16 09:04:05 +0100206
207 /**
208 * Perform one of the app actions - activate, deactivate or uninstall
209 * Raises a dialog which calls back the dOk() below
210 */
211 confirmAction(action: AppAction): void {
212 this.appAction = action;
213 const appActionLc = (<string>AppAction[this.appAction]).toLowerCase();
214
215 this.confirmMsg = this.lionFn(appActionLc) + ' ' + this.selId;
216 if (strongWarning[this.selId]) {
217 this.strongWarning = this.warnDeactivate + '\n' + this.warnOwnRisk;
Sean Condon2bd11b72018-06-15 08:00:48 +0100218 }
Sean Condon2aa86092018-07-16 09:04:05 +0100219
220 this.log.debug('Initiating', this.appAction, 'of', this.selId);
Sean Condon2bd11b72018-06-15 08:00:48 +0100221 }
222
Sean Condon2aa86092018-07-16 09:04:05 +0100223 /**
224 * Callback when the Confirm dialog is shown and a choice is made
225 */
226 dOk(choice: boolean) {
227 const appActionLc = (<string>AppAction[this.appAction]).toLowerCase();
228 if (choice) {
229 this.log.debug('Confirmed', appActionLc, 'on', this.selId);
Sean Condon2bd11b72018-06-15 08:00:48 +0100230
Sean Condon2aa86092018-07-16 09:04:05 +0100231 this.wss.sendEvent(APPMGMTREQ, {
232 action: appActionLc,
233 name: this.selId,
234 sortCol: this.sortParams.firstCol,
235 sortDir: SortDir[this.sortParams.firstDir],
Sean Condon2bd11b72018-06-15 08:00:48 +0100236 });
Sean Condon2aa86092018-07-16 09:04:05 +0100237 if (this.appAction === AppAction.UNINSTALL) {
238 this.selId = '';
Sean Condon2bd11b72018-06-15 08:00:48 +0100239 } else {
Sean Condon2aa86092018-07-16 09:04:05 +0100240 this.wss.sendEvent(DETAILSREQ, { id: this.selId });
Sean Condon2bd11b72018-06-15 08:00:48 +0100241 }
Sean Condon2bd11b72018-06-15 08:00:48 +0100242
Sean Condon2aa86092018-07-16 09:04:05 +0100243 } else {
244 this.log.debug('Cancelled', appActionLc, 'on', this.selId);
Sean Condon2bd11b72018-06-15 08:00:48 +0100245 }
Sean Condon2aa86092018-07-16 09:04:05 +0100246 this.confirmMsg = '';
247 this.strongWarning = '';
Sean Condon2bd11b72018-06-15 08:00:48 +0100248 }
249
250 downloadApp() {
251 if (this.ctrlBtnState.selection) {
Sean Condon2aa86092018-07-16 09:04:05 +0100252 (<any>this.window).location = APPURLPREFIX + this.selId + '/' + FILEDOWNLOADURL;
Sean Condon2bd11b72018-06-15 08:00:48 +0100253 }
254 }
255
256 /**
Sean Condon2aa86092018-07-16 09:04:05 +0100257 * When the file is selected this fires
258 * It passes the file on to the server through a POST request
259 * If there is an error its logged and raised to the user through Flash Component
260 */
261 fileEvent(event: any, activateImmediately?: boolean) {
262 this.log.debug('File event for', event.target.files[0]);
263 const formData = new FormData();
264 formData.append('file', event.target.files[0]);
265 let url = APPURLPREFIX + FILEUPLOADURL;
266 if (activateImmediately) {
267 url += ACTIVATEOPTION;
268 }
269 this.httpClient
270 .post<any>(APPURLPREFIX + FILEUPLOADURL, formData)
271 .subscribe(
272 data => this.log.debug(data),
273 err => {
274 this.log.warn(err.message);
275 this.alertMsg = err.message; // This will activate flash msg
276 }
277 );
278
279 }
280
281 /**
282 * When the upload button is clicked pass this on to the file input (hidden)
283 */
284 triggerForm() {
285 document.getElementById('uploadFile')
286 .dispatchEvent(new MouseEvent('click'));
287 }
288
289 /**
Sean Condon28ecc5f2018-06-25 12:50:16 +0100290 * Read the LION bundle for App and set up the lionFn
Sean Condon2bd11b72018-06-15 08:00:48 +0100291 */
292 doLion() {
293 this.lionFn = this.lion.bundle('core.view.App');
294
295 this.warnDeactivate = this.lionFn('dlg_warn_deactivate');
296 this.warnOwnRisk = this.lionFn('dlg_warn_own_risk');
297
Sean Condon28ecc5f2018-06-25 12:50:16 +0100298 this.uploadTip = this.lionFn('tt_ctl_upload');
299 this.activateTip = this.lionFn('tt_ctl_activate');
300 this.deactivateTip = this.lionFn('tt_ctl_deactivate');
301 this.uninstallTip = this.lionFn('tt_ctl_uninstall');
302 this.downloadTip = this.lionFn('tt_ctl_download');
Sean Condon2bd11b72018-06-15 08:00:48 +0100303 }
Sean Condona00bf382018-06-23 07:54:01 +0100304
Sean Condon2aa86092018-07-16 09:04:05 +0100305 onDrop(event: DragEvent) {
306 event.preventDefault();
307 event.stopPropagation();
308
309 const dt = event.dataTransfer;
310 const droppedFiles = dt.files;
311
312 this.log.debug(droppedFiles.length, 'File(s) dropped');
313 if (droppedFiles.length !== 1) {
314 this.log.error(DRAGDROPMSG1, droppedFiles.length, 'were dropped');
315 this.alertMsg = DRAGDROPMSG1;
316 return;
317 } else if (droppedFiles[0].name.slice(droppedFiles[0].name.length - 4) !== '.oar') {
318 this.log.error(DRAGDROPMSGEXT, droppedFiles[0].name, 'rejected');
319 this.alertMsg = DRAGDROPMSGEXT;
320 return;
321 }
322
323 const fileEvent = {
324 target: {
325 files: droppedFiles
326 }
327 };
328 this.fileEvent(fileEvent, true);
329 }
330
331 onDragOver(evt) {
332 evt.preventDefault();
333 evt.stopPropagation();
334 }
335
336 onDragLeave(evt) {
337 evt.preventDefault();
338 evt.stopPropagation();
Sean Condona00bf382018-06-23 07:54:01 +0100339 }
Bhavesh72ead492018-07-19 16:29:18 +0530340
341 deselectRow(event) {
342 this.log.debug('Details panel close event');
343 this.selId = event;
344 this.ctrlBtnState = <CtrlBtnState>{
345 installed: undefined,
346 active: undefined
347 };
348 }
Sean Condon83fc39f2018-04-19 18:56:13 +0100349}