| /* |
| * Copyright 2018-present Open Networking Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the 'License'); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an 'AS IS' BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| import { Component, OnInit, OnDestroy, Inject } from '@angular/core'; |
| import { HttpClient, HttpErrorResponse } from '@angular/common/http'; |
| |
| import { DialogService } from '../../../fw/layer/dialog.service'; |
| import { FnService } from '../../../fw/util/fn.service'; |
| import { IconService } from '../../../fw/svg/icon.service'; |
| import { KeyService } from '../../../fw/util/key.service'; |
| import { LionService } from '../../../fw/util/lion.service'; |
| import { LoadingService } from '../../../fw/layer/loading.service'; |
| import { LogService } from '../../../log.service'; |
| import { TableBaseImpl, TableResponse, TableFilter, SortParams, SortDir } from '../../../fw/widget/table.base'; |
| import { UrlFnService } from '../../../fw/remote/urlfn.service'; |
| import { WebSocketService } from '../../../fw/remote/websocket.service'; |
| import { TableFilterPipe } from '../../../fw/widget/tablefilter.pipe'; |
| |
| const INSTALLED = 'INSTALLED'; |
| const ACTIVE = 'ACTIVE'; |
| const APPMGMTREQ = 'appManagementRequest'; |
| const DETAILSREQ = 'appDetailsRequest'; |
| const FILEUPLOADURL = 'upload'; |
| const FILEDOWNLOADURL = 'download'; |
| const ACTIVATEOPTION = '?activate=true'; |
| const DRAGDROPMSG1 = 'Drag and drop one file at a time'; |
| const DRAGDROPMSGEXT = 'Only files ending in .oar can be dropped'; |
| |
| /** Prefix to access the REST service for applications */ |
| export const APPURLPREFIX = '../../ui/rs/applications/'; // TODO: This is a hack to work off GUIv1 URL |
| /** Suffix to access the icon of the application - gives back an image */ |
| export const ICONURLSUFFIX = '/icon'; |
| |
| const downloadSuffix = '/download'; |
| const dialogId = 'app-dialog'; |
| const dialogOpts = { |
| edge: 'right', |
| width: 400, |
| }; |
| const strongWarning = { |
| 'org.onosproject.drivers': true, |
| }; |
| const propOrder = ['id', 'state', 'category', 'version', 'origin', 'role']; |
| |
| /** |
| * Model of the data returned through the Web Socket about apps. |
| */ |
| interface AppTableResponse extends TableResponse { |
| apps: App[]; |
| } |
| |
| /** |
| * Model of the data returned through Web Socket for a single App |
| */ |
| export interface App { |
| category: string; |
| desc: string; |
| features: string[]; |
| icon: string; |
| id: string; |
| origin: string; |
| permissions: string[]; |
| readme: string; |
| required_apps: string[]; |
| role: string; |
| state: string; |
| title: string; |
| url: string; |
| version: string; |
| _iconid_state: string; |
| } |
| |
| export enum AppAction { |
| NONE = 0, |
| ACTIVATE = 1, |
| DEACTIVATE = 2, |
| UNINSTALL = 3, |
| } |
| |
| /** |
| * Model of the Control Button |
| */ |
| interface CtrlBtnState { |
| installed: boolean; |
| selection: string; |
| active: boolean; |
| } |
| |
| /** |
| * ONOS GUI -- Apps View Component extends TableBaseImpl |
| */ |
| @Component({ |
| selector: 'onos-apps', |
| templateUrl: './apps.component.html', |
| styleUrls: [ |
| './apps.component.css', './apps.theme.css', |
| '../../../fw/widget/table.css', '../../../fw/widget/table.theme.css' |
| ] |
| }) |
| export class AppsComponent extends TableBaseImpl implements OnInit, OnDestroy { |
| |
| // deferred localization strings |
| lionFn; // Function |
| warnDeactivate: string; |
| warnOwnRisk: string; |
| ctrlBtnState: CtrlBtnState; |
| appFile: any; |
| |
| uploadTip: string; |
| activateTip: string; |
| deactivateTip: string; |
| uninstallTip: string; |
| downloadTip: string; |
| alertMsg: string; |
| AppActionEnum: any = AppAction; |
| appAction: AppAction = AppAction.NONE; |
| confirmMsg: string = ''; |
| strongWarning: string = ''; |
| |
| constructor( |
| protected fs: FnService, |
| private ds: DialogService, |
| private is: IconService, |
| private ks: KeyService, |
| private lion: LionService, |
| protected ls: LoadingService, |
| protected log: LogService, |
| private ufs: UrlFnService, |
| protected wss: WebSocketService, |
| @Inject('Window') private window: Window, |
| private httpClient: HttpClient |
| ) { |
| super(fs, null, log, wss, 'app'); |
| this.responseCallback = this.appResponseCb; |
| this.parentSelCb = this.rowSelection; |
| // pre-populate sort so active apps are at the top of the list |
| this.sortParams = { |
| firstCol: 'state', |
| firstDir: SortDir.desc, |
| secondCol: 'title', |
| secondDir: SortDir.asc, |
| }; |
| // We want doLion() to be called only after the Lion |
| // service is populated (from the WebSocket) |
| // If lion is not ready we make do with a dummy function |
| // As soon a lion gets loaded this function will be replaced with |
| // the real thing |
| if (this.lion.ubercache.length === 0) { |
| this.lionFn = this.dummyLion; |
| this.lion.loadCbs.set('apps', () => this.doLion()); |
| } else { |
| this.doLion(); |
| } |
| |
| this.ctrlBtnState = <CtrlBtnState>{ |
| installed: false, |
| active: false |
| }; |
| } |
| |
| /** |
| * Initialize querying the WebSocket for App table details |
| */ |
| ngOnInit() { |
| this.init(); |
| } |
| |
| /** |
| * Stop sending queries to WebSocket |
| */ |
| ngOnDestroy() { |
| this.lion.loadCbs.delete('apps'); |
| this.destroy(); |
| this.log.debug('AppComponent destroyed'); |
| } |
| |
| /** |
| * The callback called when App data returns from WSS |
| */ |
| appResponseCb(data: AppTableResponse) { |
| this.log.debug('App response received for ', data.apps.length, 'apps'); |
| } |
| |
| /** |
| * called when a row is selected - sets the state of control icons |
| */ |
| rowSelection(event: any, selRow: any) { |
| this.ctrlBtnState.installed = this.selId && selRow && selRow.state === INSTALLED; |
| this.ctrlBtnState.active = this.selId && selRow && selRow.state === ACTIVE; |
| this.ctrlBtnState.selection = this.selId; |
| this.log.debug('Row ', this.selId, 'selected', this.ctrlBtnState); |
| } |
| |
| |
| /** |
| * Perform one of the app actions - activate, deactivate or uninstall |
| * Raises a dialog which calls back the dOk() below |
| */ |
| confirmAction(action: AppAction): void { |
| this.appAction = action; |
| const appActionLc = (<string>AppAction[this.appAction]).toLowerCase(); |
| |
| this.confirmMsg = this.lionFn(appActionLc) + ' ' + this.selId; |
| if (strongWarning[this.selId]) { |
| this.strongWarning = this.warnDeactivate + '\n' + this.warnOwnRisk; |
| } |
| |
| this.log.debug('Initiating', this.appAction, 'of', this.selId); |
| } |
| |
| /** |
| * Callback when the Confirm dialog is shown and a choice is made |
| */ |
| dOk(choice: boolean) { |
| const appActionLc = (<string>AppAction[this.appAction]).toLowerCase(); |
| if (choice) { |
| this.log.debug('Confirmed', appActionLc, 'on', this.selId); |
| |
| this.wss.sendEvent(APPMGMTREQ, { |
| action: appActionLc, |
| name: this.selId, |
| sortCol: this.sortParams.firstCol, |
| sortDir: SortDir[this.sortParams.firstDir], |
| }); |
| if (this.appAction === AppAction.UNINSTALL) { |
| this.selId = ''; |
| } else { |
| this.wss.sendEvent(DETAILSREQ, { id: this.selId }); |
| } |
| |
| } else { |
| this.log.debug('Cancelled', appActionLc, 'on', this.selId); |
| } |
| this.confirmMsg = ''; |
| this.strongWarning = ''; |
| } |
| |
| downloadApp() { |
| if (this.ctrlBtnState.selection) { |
| (<any>this.window).location = APPURLPREFIX + this.selId + '/' + FILEDOWNLOADURL; |
| } |
| } |
| |
| /** |
| * When the file is selected this fires |
| * It passes the file on to the server through a POST request |
| * If there is an error its logged and raised to the user through Flash Component |
| */ |
| fileEvent(event: any, activateImmediately?: boolean) { |
| this.log.debug('File event for', event.target.files[0]); |
| const formData = new FormData(); |
| formData.append('file', event.target.files[0]); |
| let url = APPURLPREFIX + FILEUPLOADURL; |
| if (activateImmediately) { |
| url += ACTIVATEOPTION; |
| } |
| this.httpClient |
| .post<any>(APPURLPREFIX + FILEUPLOADURL, formData) |
| .subscribe( |
| data => this.log.debug(data), |
| err => { |
| this.log.warn(err.message); |
| this.alertMsg = err.message; // This will activate flash msg |
| } |
| ); |
| |
| } |
| |
| /** |
| * When the upload button is clicked pass this on to the file input (hidden) |
| */ |
| triggerForm() { |
| document.getElementById('uploadFile') |
| .dispatchEvent(new MouseEvent('click')); |
| } |
| |
| /** |
| * Read the LION bundle for App and set up the lionFn |
| */ |
| doLion() { |
| this.lionFn = this.lion.bundle('core.view.App'); |
| |
| this.warnDeactivate = this.lionFn('dlg_warn_deactivate'); |
| this.warnOwnRisk = this.lionFn('dlg_warn_own_risk'); |
| |
| this.uploadTip = this.lionFn('tt_ctl_upload'); |
| this.activateTip = this.lionFn('tt_ctl_activate'); |
| this.deactivateTip = this.lionFn('tt_ctl_deactivate'); |
| this.uninstallTip = this.lionFn('tt_ctl_uninstall'); |
| this.downloadTip = this.lionFn('tt_ctl_download'); |
| } |
| |
| onDrop(event: DragEvent) { |
| event.preventDefault(); |
| event.stopPropagation(); |
| |
| const dt = event.dataTransfer; |
| const droppedFiles = dt.files; |
| |
| this.log.debug(droppedFiles.length, 'File(s) dropped'); |
| if (droppedFiles.length !== 1) { |
| this.log.error(DRAGDROPMSG1, droppedFiles.length, 'were dropped'); |
| this.alertMsg = DRAGDROPMSG1; |
| return; |
| } else if (droppedFiles[0].name.slice(droppedFiles[0].name.length - 4) !== '.oar') { |
| this.log.error(DRAGDROPMSGEXT, droppedFiles[0].name, 'rejected'); |
| this.alertMsg = DRAGDROPMSGEXT; |
| return; |
| } |
| |
| const fileEvent = { |
| target: { |
| files: droppedFiles |
| } |
| }; |
| this.fileEvent(fileEvent, true); |
| } |
| |
| onDragOver(evt) { |
| evt.preventDefault(); |
| evt.stopPropagation(); |
| } |
| |
| onDragLeave(evt) { |
| evt.preventDefault(); |
| evt.stopPropagation(); |
| } |
| |
| deselectRow(event) { |
| this.log.debug('Details panel close event'); |
| this.selId = event; |
| this.ctrlBtnState = <CtrlBtnState>{ |
| installed: undefined, |
| active: undefined |
| }; |
| } |
| } |