blob: 59d91ed128c7ce03c43981a4da310e30536aba88 [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 Condon95fb5742019-04-02 12:16:55 +010017import { HttpClient } from '@angular/common/http';
Sean Condon2aa86092018-07-16 09:04:05 +010018
Sean Condon5ca00262018-09-06 17:55:25 +010019import {
20 FnService,
21 IconService,
22 LionService,
Sean Condon5ca00262018-09-06 17:55:25 +010023 LogService,
Sean Condon95fb5742019-04-02 12:16:55 +010024 TableBaseImpl, TableResponse, SortDir,
Sean Condon5ca00262018-09-06 17:55:25 +010025 UrlFnService,
Sean Condon95fb5742019-04-02 12:16:55 +010026 WebSocketService
Sean Condon5ca00262018-09-06 17:55:25 +010027} from 'gui2-fw-lib';
Sean Condon83fc39f2018-04-19 18:56:13 +010028
Sean Condon2bd11b72018-06-15 08:00:48 +010029const INSTALLED = 'INSTALLED';
30const ACTIVE = 'ACTIVE';
Sean Condon2aa86092018-07-16 09:04:05 +010031const APPMGMTREQ = 'appManagementRequest';
32const DETAILSREQ = 'appDetailsRequest';
33const FILEUPLOADURL = 'upload';
34const FILEDOWNLOADURL = 'download';
35const ACTIVATEOPTION = '?activate=true';
36const DRAGDROPMSG1 = 'Drag and drop one file at a time';
37const DRAGDROPMSGEXT = 'Only files ending in .oar can be dropped';
Sean Condon28ecc5f2018-06-25 12:50:16 +010038
39/** Prefix to access the REST service for applications */
Sean Condon3c8e5582018-08-28 23:22:43 +010040export const APPURLPREFIX = 'rs/applications/';
Sean Condon28ecc5f2018-06-25 12:50:16 +010041/** Suffix to access the icon of the application - gives back an image */
42export const ICONURLSUFFIX = '/icon';
43
Sean Condon2bd11b72018-06-15 08:00:48 +010044const downloadSuffix = '/download';
45const dialogId = 'app-dialog';
46const dialogOpts = {
47 edge: 'right',
48 width: 400,
49};
50const strongWarning = {
51 'org.onosproject.drivers': true,
52};
53const propOrder = ['id', 'state', 'category', 'version', 'origin', 'role'];
54
Sean Condon28ecc5f2018-06-25 12:50:16 +010055/**
56 * Model of the data returned through the Web Socket about apps.
57 */
Sean Condon2bd11b72018-06-15 08:00:48 +010058interface AppTableResponse extends TableResponse {
Sean Condon28ecc5f2018-06-25 12:50:16 +010059 apps: App[];
Sean Condon2bd11b72018-06-15 08:00:48 +010060}
61
Sean Condon28ecc5f2018-06-25 12:50:16 +010062/**
63 * Model of the data returned through Web Socket for a single App
64 */
65export interface App {
Sean Condon2bd11b72018-06-15 08:00:48 +010066 category: string;
67 desc: string;
Sean Condon28ecc5f2018-06-25 12:50:16 +010068 features: string[];
Sean Condon2bd11b72018-06-15 08:00:48 +010069 icon: string;
70 id: string;
71 origin: string;
Sean Condon28ecc5f2018-06-25 12:50:16 +010072 permissions: string[];
Sean Condon2bd11b72018-06-15 08:00:48 +010073 readme: string;
Sean Condon28ecc5f2018-06-25 12:50:16 +010074 required_apps: string[];
Sean Condon2bd11b72018-06-15 08:00:48 +010075 role: string;
76 state: string;
77 title: string;
78 url: string;
79 version: string;
80 _iconid_state: string;
81}
82
Sean Condon2aa86092018-07-16 09:04:05 +010083export enum AppAction {
84 NONE = 0,
85 ACTIVATE = 1,
86 DEACTIVATE = 2,
87 UNINSTALL = 3,
88}
89
Sean Condon28ecc5f2018-06-25 12:50:16 +010090/**
91 * Model of the Control Button
92 */
Sean Condon2bd11b72018-06-15 08:00:48 +010093interface CtrlBtnState {
94 installed: boolean;
95 selection: string;
96 active: boolean;
97}
98
Sean Condon83fc39f2018-04-19 18:56:13 +010099/**
Sean Condon2aa86092018-07-16 09:04:05 +0100100 * ONOS GUI -- Apps View Component extends TableBaseImpl
Sean Condon83fc39f2018-04-19 18:56:13 +0100101 */
102@Component({
103 selector: 'onos-apps',
104 templateUrl: './apps.component.html',
Sean Condon2bd11b72018-06-15 08:00:48 +0100105 styleUrls: [
106 './apps.component.css', './apps.theme.css',
Sean Condon28ecc5f2018-06-25 12:50:16 +0100107 '../../../fw/widget/table.css', '../../../fw/widget/table.theme.css'
Sean Condon2bd11b72018-06-15 08:00:48 +0100108 ]
Sean Condon83fc39f2018-04-19 18:56:13 +0100109})
Sean Condon2bd11b72018-06-15 08:00:48 +0100110export class AppsComponent extends TableBaseImpl implements OnInit, OnDestroy {
111
112 // deferred localization strings
113 lionFn; // Function
114 warnDeactivate: string;
115 warnOwnRisk: string;
Sean Condon2bd11b72018-06-15 08:00:48 +0100116 ctrlBtnState: CtrlBtnState;
Sean Condona00bf382018-06-23 07:54:01 +0100117 appFile: any;
Sean Condona00bf382018-06-23 07:54:01 +0100118
119 uploadTip: string;
120 activateTip: string;
121 deactivateTip: string;
122 uninstallTip: string;
123 downloadTip: string;
Sean Condon2aa86092018-07-16 09:04:05 +0100124 alertMsg: string;
125 AppActionEnum: any = AppAction;
126 appAction: AppAction = AppAction.NONE;
127 confirmMsg: string = '';
128 strongWarning: string = '';
Sean Condon83fc39f2018-04-19 18:56:13 +0100129
130 constructor(
Sean Condon2bd11b72018-06-15 08:00:48 +0100131 protected fs: FnService,
Sean Condon83fc39f2018-04-19 18:56:13 +0100132 private is: IconService,
Sean Condon2bd11b72018-06-15 08:00:48 +0100133 private lion: LionService,
Sean Condon2bd11b72018-06-15 08:00:48 +0100134 protected log: LogService,
Sean Condon83fc39f2018-04-19 18:56:13 +0100135 private ufs: UrlFnService,
Sean Condon2bd11b72018-06-15 08:00:48 +0100136 protected wss: WebSocketService,
Sean Condona00bf382018-06-23 07:54:01 +0100137 @Inject('Window') private window: Window,
Sean Condon2aa86092018-07-16 09:04:05 +0100138 private httpClient: HttpClient
Sean Condon83fc39f2018-04-19 18:56:13 +0100139 ) {
Sean Condon95fb5742019-04-02 12:16:55 +0100140 super(fs, log, wss, 'app');
Sean Condon2bd11b72018-06-15 08:00:48 +0100141 this.responseCallback = this.appResponseCb;
Sean Condon2aa86092018-07-16 09:04:05 +0100142 this.parentSelCb = this.rowSelection;
Sean Condon28ecc5f2018-06-25 12:50:16 +0100143 // pre-populate sort so active apps are at the top of the list
Sean Condon2bd11b72018-06-15 08:00:48 +0100144 this.sortParams = {
145 firstCol: 'state',
Sean Condon28ecc5f2018-06-25 12:50:16 +0100146 firstDir: SortDir.desc,
Sean Condon2bd11b72018-06-15 08:00:48 +0100147 secondCol: 'title',
Sean Condon28ecc5f2018-06-25 12:50:16 +0100148 secondDir: SortDir.asc,
Sean Condon2bd11b72018-06-15 08:00:48 +0100149 };
Sean Condon28ecc5f2018-06-25 12:50:16 +0100150 // We want doLion() to be called only after the Lion
151 // service is populated (from the WebSocket)
152 // If lion is not ready we make do with a dummy function
153 // As soon a lion gets loaded this function will be replaced with
154 // the real thing
155 if (this.lion.ubercache.length === 0) {
156 this.lionFn = this.dummyLion;
157 this.lion.loadCbs.set('apps', () => this.doLion());
158 } else {
159 this.doLion();
160 }
161
Sean Condon2bd11b72018-06-15 08:00:48 +0100162 this.ctrlBtnState = <CtrlBtnState>{
163 installed: false,
164 active: false
165 };
Sean Condon83fc39f2018-04-19 18:56:13 +0100166 }
167
Sean Condon28ecc5f2018-06-25 12:50:16 +0100168 /**
169 * Initialize querying the WebSocket for App table details
170 */
Sean Condon83fc39f2018-04-19 18:56:13 +0100171 ngOnInit() {
Sean Condon2bd11b72018-06-15 08:00:48 +0100172 this.init();
Sean Condon83fc39f2018-04-19 18:56:13 +0100173 }
174
Sean Condon28ecc5f2018-06-25 12:50:16 +0100175 /**
176 * Stop sending queries to WebSocket
177 */
Sean Condon2bd11b72018-06-15 08:00:48 +0100178 ngOnDestroy() {
Sean Condon28ecc5f2018-06-25 12:50:16 +0100179 this.lion.loadCbs.delete('apps');
Sean Condon2bd11b72018-06-15 08:00:48 +0100180 this.destroy();
181 this.log.debug('AppComponent destroyed');
182 }
183
184 /**
185 * The callback called when App data returns from WSS
186 */
187 appResponseCb(data: AppTableResponse) {
188 this.log.debug('App response received for ', data.apps.length, 'apps');
189 }
190
Sean Condon2aa86092018-07-16 09:04:05 +0100191 /**
192 * called when a row is selected - sets the state of control icons
193 */
194 rowSelection(event: any, selRow: any) {
195 this.ctrlBtnState.installed = this.selId && selRow && selRow.state === INSTALLED;
196 this.ctrlBtnState.active = this.selId && selRow && selRow.state === ACTIVE;
197 this.ctrlBtnState.selection = this.selId;
198 this.log.debug('Row ', this.selId, 'selected', this.ctrlBtnState);
199 }
Sean Condon2bd11b72018-06-15 08:00:48 +0100200
Sean Condon2aa86092018-07-16 09:04:05 +0100201
202 /**
203 * Perform one of the app actions - activate, deactivate or uninstall
204 * Raises a dialog which calls back the dOk() below
205 */
206 confirmAction(action: AppAction): void {
207 this.appAction = action;
208 const appActionLc = (<string>AppAction[this.appAction]).toLowerCase();
209
210 this.confirmMsg = this.lionFn(appActionLc) + ' ' + this.selId;
211 if (strongWarning[this.selId]) {
212 this.strongWarning = this.warnDeactivate + '\n' + this.warnOwnRisk;
Sean Condon2bd11b72018-06-15 08:00:48 +0100213 }
Sean Condon2aa86092018-07-16 09:04:05 +0100214
215 this.log.debug('Initiating', this.appAction, 'of', this.selId);
Sean Condon2bd11b72018-06-15 08:00:48 +0100216 }
217
Sean Condon2aa86092018-07-16 09:04:05 +0100218 /**
219 * Callback when the Confirm dialog is shown and a choice is made
220 */
221 dOk(choice: boolean) {
222 const appActionLc = (<string>AppAction[this.appAction]).toLowerCase();
223 if (choice) {
224 this.log.debug('Confirmed', appActionLc, 'on', this.selId);
Sean Condon2bd11b72018-06-15 08:00:48 +0100225
Sean Condon2aa86092018-07-16 09:04:05 +0100226 this.wss.sendEvent(APPMGMTREQ, {
227 action: appActionLc,
228 name: this.selId,
229 sortCol: this.sortParams.firstCol,
230 sortDir: SortDir[this.sortParams.firstDir],
Sean Condon2bd11b72018-06-15 08:00:48 +0100231 });
Sean Condon2aa86092018-07-16 09:04:05 +0100232 if (this.appAction === AppAction.UNINSTALL) {
233 this.selId = '';
Sean Condon2bd11b72018-06-15 08:00:48 +0100234 } else {
Sean Condon2aa86092018-07-16 09:04:05 +0100235 this.wss.sendEvent(DETAILSREQ, { id: this.selId });
Sean Condon2bd11b72018-06-15 08:00:48 +0100236 }
Sean Condon2bd11b72018-06-15 08:00:48 +0100237
Sean Condon2aa86092018-07-16 09:04:05 +0100238 } else {
239 this.log.debug('Cancelled', appActionLc, 'on', this.selId);
Sean Condon2bd11b72018-06-15 08:00:48 +0100240 }
Sean Condon2aa86092018-07-16 09:04:05 +0100241 this.confirmMsg = '';
242 this.strongWarning = '';
Sean Condon2bd11b72018-06-15 08:00:48 +0100243 }
244
245 downloadApp() {
246 if (this.ctrlBtnState.selection) {
Sean Condon2aa86092018-07-16 09:04:05 +0100247 (<any>this.window).location = APPURLPREFIX + this.selId + '/' + FILEDOWNLOADURL;
Sean Condon2bd11b72018-06-15 08:00:48 +0100248 }
249 }
250
251 /**
Sean Condon2aa86092018-07-16 09:04:05 +0100252 * When the file is selected this fires
253 * It passes the file on to the server through a POST request
254 * If there is an error its logged and raised to the user through Flash Component
255 */
256 fileEvent(event: any, activateImmediately?: boolean) {
257 this.log.debug('File event for', event.target.files[0]);
258 const formData = new FormData();
259 formData.append('file', event.target.files[0]);
260 let url = APPURLPREFIX + FILEUPLOADURL;
261 if (activateImmediately) {
262 url += ACTIVATEOPTION;
263 }
264 this.httpClient
265 .post<any>(APPURLPREFIX + FILEUPLOADURL, formData)
266 .subscribe(
267 data => this.log.debug(data),
268 err => {
269 this.log.warn(err.message);
270 this.alertMsg = err.message; // This will activate flash msg
271 }
272 );
273
274 }
275
276 /**
277 * When the upload button is clicked pass this on to the file input (hidden)
278 */
279 triggerForm() {
280 document.getElementById('uploadFile')
281 .dispatchEvent(new MouseEvent('click'));
282 }
283
284 /**
Sean Condon28ecc5f2018-06-25 12:50:16 +0100285 * Read the LION bundle for App and set up the lionFn
Sean Condon2bd11b72018-06-15 08:00:48 +0100286 */
287 doLion() {
288 this.lionFn = this.lion.bundle('core.view.App');
289
290 this.warnDeactivate = this.lionFn('dlg_warn_deactivate');
291 this.warnOwnRisk = this.lionFn('dlg_warn_own_risk');
292
Sean Condon28ecc5f2018-06-25 12:50:16 +0100293 this.uploadTip = this.lionFn('tt_ctl_upload');
294 this.activateTip = this.lionFn('tt_ctl_activate');
295 this.deactivateTip = this.lionFn('tt_ctl_deactivate');
296 this.uninstallTip = this.lionFn('tt_ctl_uninstall');
297 this.downloadTip = this.lionFn('tt_ctl_download');
Sean Condon2bd11b72018-06-15 08:00:48 +0100298 }
Sean Condona00bf382018-06-23 07:54:01 +0100299
Sean Condon2aa86092018-07-16 09:04:05 +0100300 onDrop(event: DragEvent) {
301 event.preventDefault();
302 event.stopPropagation();
303
304 const dt = event.dataTransfer;
305 const droppedFiles = dt.files;
306
307 this.log.debug(droppedFiles.length, 'File(s) dropped');
308 if (droppedFiles.length !== 1) {
309 this.log.error(DRAGDROPMSG1, droppedFiles.length, 'were dropped');
310 this.alertMsg = DRAGDROPMSG1;
311 return;
312 } else if (droppedFiles[0].name.slice(droppedFiles[0].name.length - 4) !== '.oar') {
313 this.log.error(DRAGDROPMSGEXT, droppedFiles[0].name, 'rejected');
314 this.alertMsg = DRAGDROPMSGEXT;
315 return;
316 }
317
318 const fileEvent = {
319 target: {
320 files: droppedFiles
321 }
322 };
323 this.fileEvent(fileEvent, true);
324 }
325
326 onDragOver(evt) {
327 evt.preventDefault();
328 evt.stopPropagation();
329 }
330
331 onDragLeave(evt) {
332 evt.preventDefault();
333 evt.stopPropagation();
Sean Condona00bf382018-06-23 07:54:01 +0100334 }
Bhavesh72ead492018-07-19 16:29:18 +0530335
336 deselectRow(event) {
337 this.log.debug('Details panel close event');
338 this.selId = event;
339 this.ctrlBtnState = <CtrlBtnState>{
340 installed: undefined,
341 active: undefined
342 };
343 }
Sean Condon83fc39f2018-04-19 18:56:13 +0100344}