3 * Copyright (C) 2018 "IoT.bzh"
4 * Author Sebastien Douheret <sebastien@iot.bzh>
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 import { Injectable, SecurityContext, isDevMode } from '@angular/core';
20 import { Observable } from 'rxjs/Observable';
21 import { Subject } from 'rxjs/Subject';
22 import { BehaviorSubject } from 'rxjs/BehaviorSubject';
24 import { XDSAgentService, IXDSTargetConfig, IXDSTargetTerminal } from '../services/xdsagent.service';
26 /* FIXME: syntax only compatible with TS>2.4.0
27 export enum TargetTypeEnum {
32 export type TargetTypeEnum = '' | 'standard';
33 export const TargetType = {
34 UNSET: <TargetTypeEnum>'',
35 STANDARD: <TargetTypeEnum>'standard',
38 export const TargetTypes = [
39 { value: TargetType.STANDARD, display: 'Standard' },
42 export const TargetStatus = {
43 ErrorConfig: 'ErrorConfig',
48 export type TerminalTypeEnum = '' | 'ssh';
49 export const TerminalType = {
50 UNSET: <TerminalTypeEnum>'',
51 SSH: <TerminalTypeEnum>'ssh',
54 export interface ITarget extends IXDSTargetConfig {
58 export interface ITerminal extends IXDSTargetTerminal {
62 export interface ITerminalOutput {
69 export interface ITerminalExit {
77 export class TargetService {
78 public targets$: Observable<ITarget[]>;
79 public curTarget$: Observable<ITarget>;
80 public terminalOutput$ = <Subject<ITerminalOutput>>new Subject();
81 public terminalExit$ = <Subject<ITerminalExit>>new Subject();
83 private _tgtsList: ITarget[] = [];
84 private tgtsSubject = <BehaviorSubject<ITarget[]>>new BehaviorSubject(this._tgtsList);
85 private _current: ITarget;
86 private curTgtSubject = <BehaviorSubject<ITarget>>new BehaviorSubject(this._current);
88 private termSocket: SocketIOClient.Socket;
90 constructor(private xdsSvr: XDSAgentService) {
92 this.targets$ = this.tgtsSubject.asObservable();
93 this.curTarget$ = this.curTgtSubject.asObservable();
95 this.xdsSvr.XdsConfig$.subscribe(cfg => {
96 if (!cfg || cfg.servers.length < 1) {
100 // FIXME support multiple server
101 this.curServerID = cfg.servers[0].id;
103 // Load initial targets list
104 this.xdsSvr.getTargets(this.curServerID).subscribe((targets) => {
106 targets.forEach(p => {
107 this._addTarget(p, true);
110 // TODO: get previous val from xds-config service / cookie
111 if (this._tgtsList.length > 0) {
112 this._current = this._tgtsList[0];
113 this.curTgtSubject.next(this._current);
116 this.tgtsSubject.next(this._tgtsList);
120 // Add listener on targets creation, deletion and change events
121 this.xdsSvr.onTargetAdd().subscribe(tgt => this._addTarget(tgt));
122 this.xdsSvr.onTargetDelete().subscribe(tgt => this._delTarget(tgt));
123 this.xdsSvr.onTargetChange().subscribe(tgt => this._updateTarget(tgt));
125 // Register events to forward terminal Output and Exit
126 this.xdsSvr.onSocketConnect().subscribe(socket => {
127 this.termSocket = socket;
129 // Handle terminal output
130 socket.on('term:output', data => {
131 const termOut = <ITerminalOutput>{
133 timestamp: data.timestamp,
134 stdout: atob(data.stdout),
135 stderr: atob(data.stderr),
137 this.terminalOutput$.next(termOut);
140 // Handle terminal exit event
141 socket.on('term:exit', data => {
142 this.terminalExit$.next(Object.assign({}, <ITerminalExit>data));
148 setCurrent(p: ITarget): ITarget | undefined {
150 this._current = null;
153 return this.setCurrentById(p.id);
156 setCurrentById(id: string): ITarget | undefined {
157 const p = this._tgtsList.find(item => item.id === id);
160 this.curTgtSubject.next(this._current);
162 return this._current;
165 getCurrent(): ITarget {
166 return this._current;
169 getTargetById(id: string): ITarget | undefined {
170 const t = this._tgtsList.find(item => item.id === id);
174 add(tgt: ITarget): Observable<ITarget> {
175 return this.xdsSvr.addTarget(this.curServerID, tgt);
178 delete(tgt: ITarget): Observable<ITarget> {
179 const idx = this._getTargetIdx(tgt.id);
182 throw new Error('Invalid target id (id=' + tgt.id + ')');
184 return this.xdsSvr.deleteTarget(this.curServerID, tgt.id)
188 setSettings(tgt: ITarget): Observable<ITarget> {
189 return this.xdsSvr.updateTarget(this.curServerID, tgt);
192 terminalOpen(tgtID: string, termID: string, cfg?: IXDSTargetTerminal): Observable<IXDSTargetTerminal> {
193 if (termID === '' || termID === undefined) {
194 // create a new terminal when no termID is set
195 if (cfg === undefined) {
196 cfg = <IXDSTargetTerminal>{
197 name: 'ssh to ' + this.getTargetById(tgtID).name,
198 type: TerminalType.SSH,
201 return this.xdsSvr.createTerminalTarget(this.curServerID, tgtID, cfg)
203 return this.xdsSvr.openTerminalTarget(this.curServerID, tgtID, res.id);
206 return this.xdsSvr.openTerminalTarget(this.curServerID, tgtID, termID);
210 terminalClose(tgtID, termID: string): Observable<IXDSTargetTerminal> {
211 return this.xdsSvr.closeTerminalTarget(this.curServerID, tgtID, termID);
214 terminalWrite(data: string) {
215 if (this.termSocket) {
216 this.termSocket.emit('term:input', btoa(data));
220 terminalResize(tgtID, termID: string, cols, rows: number): Observable<IXDSTargetTerminal> {
221 return this.xdsSvr.resizeTerminalTarget(this.curServerID, tgtID, termID, cols, rows);
224 /*** Private functions ***/
226 private _isUsableTarget(p) {
227 return p && (p.status === TargetStatus.Enable);
230 private _getTargetIdx(id: string): number {
231 return this._tgtsList.findIndex((item) => item.id === id);
234 private _addTarget(tgt: ITarget, noNext?: boolean): ITarget {
236 tgt.isUsable = this._isUsableTarget(tgt);
239 this._tgtsList.push(tgt);
242 this._tgtsList.sort((a, b) => {
243 if (a.name < b.name) {
246 if (a.name > b.name) {
253 this.tgtsSubject.next(this._tgtsList);
259 private _delTarget(tgt: ITarget) {
260 const idx = this._tgtsList.findIndex(item => item.id === tgt.id);
263 /* tslint:disable:no-console */
264 console.log('Warning: Try to delete target unknown id: tgt=', tgt);
268 const delId = this._tgtsList[idx].id;
269 this._tgtsList.splice(idx, 1);
270 if (delId === this._current.id) {
271 this.setCurrent(this._tgtsList[0]);
273 this.tgtsSubject.next(this._tgtsList);
276 private _updateTarget(tgt: ITarget) {
277 const i = this._getTargetIdx(tgt.id);
279 this._tgtsList[i].status = tgt.status;
280 this._tgtsList[i].isUsable = this._isUsableTarget(tgt);
281 this.tgtsSubject.next(this._tgtsList);