6cd9ba35ab06aac6ae5801618874e2aa9cfd552f
[src/xds/xds-server.git] / webapp / src / app / common / xdsserver.service.ts
1 import { Injectable } from '@angular/core';
2 import { Http, Headers, RequestOptionsArgs, Response } from '@angular/http';
3 import { Location } from '@angular/common';
4 import { Observable } from 'rxjs/Observable';
5 import { Subject } from 'rxjs/Subject';
6 import { BehaviorSubject } from 'rxjs/BehaviorSubject';
7 import * as io from 'socket.io-client';
8
9 import { AlertService } from './alert.service';
10 import { ISdk } from './sdk.service';
11
12
13 // Import RxJs required methods
14 import 'rxjs/add/operator/map';
15 import 'rxjs/add/operator/catch';
16 import 'rxjs/add/observable/throw';
17 import 'rxjs/add/operator/mergeMap';
18
19
20 export interface IXDSConfigProject {
21     id: string;
22     path: string;
23     hostSyncThingID: string;
24     label?: string;
25     defaultSdkID?: string;
26 }
27
28 interface IXDSBuilderConfig {
29     ip: string;
30     port: string;
31     syncThingID: string;
32 }
33
34 interface IXDSFolderConfig {
35     id: string;
36     label: string;
37     path: string;
38     type: number;
39     syncThingID: string;
40     builderSThgID?: string;
41     status?: string;
42     defaultSdkID: string;
43 }
44
45 interface IXDSConfig {
46     version: number;
47     builder: IXDSBuilderConfig;
48     folders: IXDSFolderConfig[];
49 }
50
51 export interface ISdkMessage {
52     wsID: string;
53     msgType: string;
54     data: any;
55 }
56
57 export interface ICmdOutput {
58     cmdID: string;
59     timestamp: string;
60     stdout: string;
61     stderr: string;
62 }
63
64 export interface ICmdExit {
65     cmdID: string;
66     timestamp: string;
67     code: number;
68     error: string;
69 }
70
71 export interface IServerStatus {
72     WS_connected: boolean;
73
74 }
75
76 const FOLDER_TYPE_CLOUDSYNC = 2;
77
78 @Injectable()
79 export class XDSServerService {
80
81     public CmdOutput$ = <Subject<ICmdOutput>>new Subject();
82     public CmdExit$ = <Subject<ICmdExit>>new Subject();
83     public Status$: Observable<IServerStatus>;
84
85     private baseUrl: string;
86     private wsUrl: string;
87     private _status = { WS_connected: false };
88     private statusSubject = <BehaviorSubject<IServerStatus>>new BehaviorSubject(this._status);
89
90
91     private socket: SocketIOClient.Socket;
92
93     constructor(private http: Http, private _window: Window, private alert: AlertService) {
94
95         this.Status$ = this.statusSubject.asObservable();
96
97         this.baseUrl = this._window.location.origin + '/api/v1';
98         let re = this._window.location.origin.match(/http[s]?:\/\/([^\/]*)[\/]?/);
99         if (re === null || re.length < 2) {
100             console.error('ERROR: cannot determine Websocket url');
101         } else {
102             this.wsUrl = 'ws://' + re[1];
103             this._handleIoSocket();
104         }
105     }
106
107     private _WSState(sts: boolean) {
108         this._status.WS_connected = sts;
109         this.statusSubject.next(Object.assign({}, this._status));
110     }
111
112     private _handleIoSocket() {
113         this.socket = io(this.wsUrl, { transports: ['websocket'] });
114
115         this.socket.on('connect_error', (res) => {
116             this._WSState(false);
117             console.error('WS Connect_error ', res);
118         });
119
120         this.socket.on('connect', (res) => {
121             this._WSState(true);
122         });
123
124         this.socket.on('disconnection', (res) => {
125             this._WSState(false);
126             this.alert.error('WS disconnection: ' + res);
127         });
128
129         this.socket.on('error', (err) => {
130             console.error('WS error:', err);
131         });
132
133         this.socket.on('make:output', data => {
134             this.CmdOutput$.next(Object.assign({}, <ICmdOutput>data));
135         });
136
137         this.socket.on('make:exit', data => {
138             this.CmdExit$.next(Object.assign({}, <ICmdExit>data));
139         });
140
141     }
142
143     getSdks(): Observable<ISdk[]> {
144         return this._get('/sdks');
145     }
146
147     getProjects(): Observable<IXDSFolderConfig[]> {
148         return this._get('/folders');
149     }
150
151     addProject(cfg: IXDSConfigProject): Observable<IXDSFolderConfig> {
152         let folder: IXDSFolderConfig = {
153             id: cfg.id || null,
154             label: cfg.label || "",
155             path: cfg.path,
156             type: FOLDER_TYPE_CLOUDSYNC,
157             syncThingID: cfg.hostSyncThingID,
158             defaultSdkID: cfg.defaultSdkID || "",
159         };
160         return this._post('/folder', folder);
161     }
162
163     deleteProject(id: string): Observable<IXDSFolderConfig> {
164         return this._delete('/folder/' + id);
165     }
166
167     exec(cmd: string, args?: string[], options?: any): Observable<any> {
168         return this._post('/exec',
169             {
170                 cmd: cmd,
171                 args: args || []
172             });
173     }
174
175     make(prjID: string, dir: string, args: string, sdkid?: string): Observable<any> {
176         return this._post('/make', { id: prjID, rpath: dir, args: args, sdkid: sdkid });
177     }
178
179
180     private _attachAuthHeaders(options?: any) {
181         options = options || {};
182         let headers = options.headers || new Headers();
183         // headers.append('Authorization', 'Basic ' + btoa('username:password'));
184         headers.append('Accept', 'application/json');
185         headers.append('Content-Type', 'application/json');
186         // headers.append('Access-Control-Allow-Origin', '*');
187
188         options.headers = headers;
189         return options;
190     }
191
192     private _get(url: string): Observable<any> {
193         return this.http.get(this.baseUrl + url, this._attachAuthHeaders())
194             .map((res: Response) => res.json())
195             .catch(this._decodeError);
196     }
197     private _post(url: string, body: any): Observable<any> {
198         return this.http.post(this.baseUrl + url, JSON.stringify(body), this._attachAuthHeaders())
199             .map((res: Response) => res.json())
200             .catch((error) => {
201                 return this._decodeError(error);
202             });
203     }
204     private _delete(url: string): Observable<any> {
205         return this.http.delete(this.baseUrl + url, this._attachAuthHeaders())
206             .map((res: Response) => res.json())
207             .catch(this._decodeError);
208     }
209
210     private _decodeError(err: any) {
211         let e: string;
212         if (typeof err === "object") {
213             if (err.statusText) {
214                 e = err.statusText;
215             } else if (err.error) {
216                 e = String(err.error);
217             } else {
218                 e = JSON.stringify(err);
219             }
220         } else {
221             e = err.json().error || 'Server error';
222         }
223         return Observable.throw(e);
224     }
225 }