diff options
Diffstat (limited to 'webapp/src/app/@core-xds/services/target.service.ts')
-rw-r--r-- | webapp/src/app/@core-xds/services/target.service.ts | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/webapp/src/app/@core-xds/services/target.service.ts b/webapp/src/app/@core-xds/services/target.service.ts new file mode 100644 index 0000000..9c995ea --- /dev/null +++ b/webapp/src/app/@core-xds/services/target.service.ts @@ -0,0 +1,285 @@ +/** +* @license +* Copyright (C) 2018 "IoT.bzh" +* Author Sebastien Douheret <sebastien@iot.bzh> +* +* 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 { Injectable, SecurityContext, isDevMode } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/Subject'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +import { XDSAgentService, IXDSTargetConfig, IXDSTargetTerminal } from '../services/xdsagent.service'; + +/* FIXME: syntax only compatible with TS>2.4.0 +export enum TargetTypeEnum { + UNSET = '', + STANDARD: 'standard', +} +*/ +export type TargetTypeEnum = '' | 'standard'; +export const TargetType = { + UNSET: <TargetTypeEnum>'', + STANDARD: <TargetTypeEnum>'standard', +}; + +export const TargetTypes = [ + { value: TargetType.STANDARD, display: 'Standard' }, +]; + +export const TargetStatus = { + ErrorConfig: 'ErrorConfig', + Disable: 'Disable', + Enable: 'Enable', +}; + +export type TerminalTypeEnum = '' | 'ssh'; +export const TerminalType = { + UNSET: <TerminalTypeEnum>'', + SSH: <TerminalTypeEnum>'ssh', +}; + +export interface ITarget extends IXDSTargetConfig { + isUsable?: boolean; +} + +export interface ITerminal extends IXDSTargetTerminal { + targetID?: string; +} + +export interface ITerminalOutput { + termID: string; + timestamp: string; + stdout: string; + stderr: string; +} + +export interface ITerminalExit { + termID: string; + timestamp: string; + code: number; + error: string; +} + +@Injectable() +export class TargetService { + public targets$: Observable<ITarget[]>; + public curTarget$: Observable<ITarget>; + public terminalOutput$ = <Subject<ITerminalOutput>>new Subject(); + public terminalExit$ = <Subject<ITerminalExit>>new Subject(); + + private _tgtsList: ITarget[] = []; + private tgtsSubject = <BehaviorSubject<ITarget[]>>new BehaviorSubject(this._tgtsList); + private _current: ITarget; + private curTgtSubject = <BehaviorSubject<ITarget>>new BehaviorSubject(this._current); + private curServerID; + private termSocket: SocketIOClient.Socket; + + constructor(private xdsSvr: XDSAgentService) { + this._current = null; + this.targets$ = this.tgtsSubject.asObservable(); + this.curTarget$ = this.curTgtSubject.asObservable(); + + this.xdsSvr.XdsConfig$.subscribe(cfg => { + if (!cfg || cfg.servers.length < 1) { + return; + } + + // FIXME support multiple server + this.curServerID = cfg.servers[0].id; + + // Load initial targets list + this.xdsSvr.getTargets(this.curServerID).subscribe((targets) => { + this._tgtsList = []; + targets.forEach(p => { + this._addTarget(p, true); + }); + + // TODO: get previous val from xds-config service / cookie + if (this._tgtsList.length > 0) { + this._current = this._tgtsList[0]; + this.curTgtSubject.next(this._current); + } + + this.tgtsSubject.next(this._tgtsList); + }); + }); + + // Add listener on targets creation, deletion and change events + this.xdsSvr.onTargetAdd().subscribe(tgt => this._addTarget(tgt)); + this.xdsSvr.onTargetDelete().subscribe(tgt => this._delTarget(tgt)); + this.xdsSvr.onTargetChange().subscribe(tgt => this._updateTarget(tgt)); + + // Register events to forward terminal Output and Exit + this.xdsSvr.onSocketConnect().subscribe(socket => { + this.termSocket = socket; + + // Handle terminal output + socket.on('term:output', data => { + const termOut = <ITerminalOutput>{ + termID: data.termID, + timestamp: data.timestamp, + stdout: atob(data.stdout), + stderr: atob(data.stderr), + }; + this.terminalOutput$.next(termOut); + }); + + // Handle terminal exit event + socket.on('term:exit', data => { + this.terminalExit$.next(Object.assign({}, <ITerminalExit>data)); + }); + + }); + } + + setCurrent(p: ITarget): ITarget | undefined { + if (!p) { + this._current = null; + return undefined; + } + return this.setCurrentById(p.id); + } + + setCurrentById(id: string): ITarget | undefined { + const p = this._tgtsList.find(item => item.id === id); + if (p) { + this._current = p; + this.curTgtSubject.next(this._current); + } + return this._current; + } + + getCurrent(): ITarget { + return this._current; + } + + getTargetById(id: string): ITarget | undefined { + const t = this._tgtsList.find(item => item.id === id); + return t; + } + + add(tgt: ITarget): Observable<ITarget> { + return this.xdsSvr.addTarget(this.curServerID, tgt); + } + + delete(tgt: ITarget): Observable<ITarget> { + const idx = this._getTargetIdx(tgt.id); + const delTgt = tgt; + if (idx === -1) { + throw new Error('Invalid target id (id=' + tgt.id + ')'); + } + return this.xdsSvr.deleteTarget(this.curServerID, tgt.id) + .map(res => delTgt); + } + + setSettings(tgt: ITarget): Observable<ITarget> { + return this.xdsSvr.updateTarget(this.curServerID, tgt); + } + + terminalOpen(tgtID: string, termID: string, cfg?: IXDSTargetTerminal): Observable<IXDSTargetTerminal> { + if (termID === '' || termID === undefined) { + // create a new terminal when no termID is set + if (cfg === undefined) { + cfg = <IXDSTargetTerminal>{ + name: 'ssh to ' + this.getTargetById(tgtID).name, + type: TerminalType.SSH, + }; + } + return this.xdsSvr.createTerminalTarget(this.curServerID, tgtID, cfg) + .flatMap(res => { + return this.xdsSvr.openTerminalTarget(this.curServerID, tgtID, res.id); + }); + } else { + return this.xdsSvr.openTerminalTarget(this.curServerID, tgtID, termID); + } + } + + terminalClose(tgtID, termID: string): Observable<IXDSTargetTerminal> { + return this.xdsSvr.closeTerminalTarget(this.curServerID, tgtID, termID); + } + + terminalWrite(data: string) { + if (this.termSocket) { + this.termSocket.emit('term:input', btoa(data)); + } + } + + terminalResize(tgtID, termID: string, cols, rows: number): Observable<IXDSTargetTerminal> { + return this.xdsSvr.resizeTerminalTarget(this.curServerID, tgtID, termID, cols, rows); + } + + /*** Private functions ***/ + + private _isUsableTarget(p) { + return p && (p.status === TargetStatus.Enable); + } + + private _getTargetIdx(id: string): number { + return this._tgtsList.findIndex((item) => item.id === id); + } + + private _addTarget(tgt: ITarget, noNext?: boolean): ITarget { + + tgt.isUsable = this._isUsableTarget(tgt); + + // add new target + this._tgtsList.push(tgt); + + // sort target array + this._tgtsList.sort((a, b) => { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; + }); + + if (!noNext) { + this.tgtsSubject.next(this._tgtsList); + } + + return tgt; + } + + private _delTarget(tgt: ITarget) { + const idx = this._tgtsList.findIndex(item => item.id === tgt.id); + if (idx === -1) { + if (isDevMode) { + /* tslint:disable:no-console */ + console.log('Warning: Try to delete target unknown id: tgt=', tgt); + } + return; + } + const delId = this._tgtsList[idx].id; + this._tgtsList.splice(idx, 1); + if (delId === this._current.id) { + this.setCurrent(this._tgtsList[0]); + } + this.tgtsSubject.next(this._tgtsList); + } + + private _updateTarget(tgt: ITarget) { + const i = this._getTargetIdx(tgt.id); + if (i >= 0) { + this._tgtsList[i].status = tgt.status; + this._tgtsList[i].isUsable = this._isUsableTarget(tgt); + this.tgtsSubject.next(this._tgtsList); + } + } + +} |