4d20fa4e84d102d2f7678d8ef54003527a021418
[src/xds/xds-server.git] / webapp / src / app / services / 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 IXDSAgentTarball {
52     os: string;
53     arch: string;
54     version: string;
55     rawVersion: string;
56     fileUrl: string;
57 }
58
59 export interface IXDSAgentInfo {
60     tarballs: IXDSAgentTarball[];
61 }
62
63 export interface ISdkMessage {
64     wsID: string;
65     msgType: string;
66     data: any;
67 }
68
69 export interface ICmdOutput {
70     cmdID: string;
71     timestamp: string;
72     stdout: string;
73     stderr: string;
74 }
75
76 export interface ICmdExit {
77     cmdID: string;
78     timestamp: string;
79     code: number;
80     error: string;
81 }
82
83 export interface IServerStatus {
84     WS_connected: boolean;
85
86 }
87
88 const FOLDER_TYPE_CLOUDSYNC = 2;
89
90 @Injectable()
91 export class XDSServerService {
92
93     public CmdOutput$ = <Subject<ICmdOutput>>new Subject();
94     public CmdExit$ = <Subject<ICmdExit>>new Subject();
95     public Status$: Observable<IServerStatus>;
96
97     private baseUrl: string;
98     private wsUrl: string;
99     private _status = { WS_connected: false };
100     private statusSubject = <BehaviorSubject<IServerStatus>>new BehaviorSubject(this._status);
101
102
103     private socket: SocketIOClient.Socket;
104
105     constructor(private http: Http, private _window: Window, private alert: AlertService) {
106
107         this.Status$ = this.statusSubject.asObservable();
108
109         this.baseUrl = this._window.location.origin + '/api/v1';
110         let re = this._window.location.origin.match(/http[s]?:\/\/([^\/]*)[\/]?/);
111         if (re === null || re.length < 2) {
112             console.error('ERROR: cannot determine Websocket url');
113         } else {
114             this.wsUrl = 'ws://' + re[1];
115             this._handleIoSocket();
116         }
117     }
118
119     private _WSState(sts: boolean) {
120         this._status.WS_connected = sts;
121         this.statusSubject.next(Object.assign({}, this._status));
122     }
123
124     private _handleIoSocket() {
125         this.socket = io(this.wsUrl, { transports: ['websocket'] });
126
127         this.socket.on('connect_error', (res) => {
128             this._WSState(false);
129             console.error('WS Connect_error ', res);
130         });
131
132         this.socket.on('connect', (res) => {
133             this._WSState(true);
134         });
135
136         this.socket.on('disconnection', (res) => {
137             this._WSState(false);
138             this.alert.error('WS disconnection: ' + res);
139         });
140
141         this.socket.on('error', (err) => {
142             console.error('WS error:', err);
143         });
144
145         this.socket.on('make:output', data => {
146             this.CmdOutput$.next(Object.assign({}, <ICmdOutput>data));
147         });
148
149         this.socket.on('make:exit', data => {
150             this.CmdExit$.next(Object.assign({}, <ICmdExit>data));
151         });
152
153         this.socket.on('exec:output', data => {
154             this.CmdOutput$.next(Object.assign({}, <ICmdOutput>data));
155         });
156
157         this.socket.on('exec:exit', data => {
158             this.CmdExit$.next(Object.assign({}, <ICmdExit>data));
159         });
160
161     }
162
163     getSdks(): Observable<ISdk[]> {
164         return this._get('/sdks');
165     }
166
167     getXdsAgentInfo(): Observable<IXDSAgentInfo> {
168         return this._get('/xdsagent/info');
169     }
170
171     getProjects(): Observable<IXDSFolderConfig[]> {
172         return this._get('/folders');
173     }
174
175     addProject(cfg: IXDSConfigProject): Observable<IXDSFolderConfig> {
176         let folder: IXDSFolderConfig = {
177             id: cfg.id || null,
178             label: cfg.label || "",
179             path: cfg.path,
180             type: FOLDER_TYPE_CLOUDSYNC,
181             syncThingID: cfg.hostSyncThingID,
182             defaultSdkID: cfg.defaultSdkID || "",
183         };
184         return this._post('/folder', folder);
185     }
186
187     deleteProject(id: string): Observable<IXDSFolderConfig> {
188         return this._delete('/folder/' + id);
189     }
190
191     exec(prjID: string, dir: string, cmd: string, sdkid?: string, args?: string[], env?: string[]): Observable<any> {
192         return this._post('/exec',
193             {
194                 id: prjID,
195                 rpath: dir,
196                 cmd: cmd,
197                 sdkid: sdkid || "",
198                 args: args || [],
199                 env: env || [],
200             });
201     }
202
203     make(prjID: string, dir: string, sdkid?: string, args?: string[], env?: string[]): Observable<any> {
204         return this._post('/make',
205             {
206                 id: prjID,
207                 rpath: dir,
208                 sdkid: sdkid,
209                 args: args || [],
210                 env: env || [],
211             });
212     }
213
214
215     private _attachAuthHeaders(options?: any) {
216         options = options || {};
217         let headers = options.headers || new Headers();
218         // headers.append('Authorization', 'Basic ' + btoa('username:password'));
219         headers.append('Accept', 'application/json');
220         headers.append('Content-Type', 'application/json');
221         // headers.append('Access-Control-Allow-Origin', '*');
222
223         options.headers = headers;
224         return options;
225     }
226
227     private _get(url: string): Observable<any> {
228         return this.http.get(this.baseUrl + url, this._attachAuthHeaders())
229             .map((res: Response) => res.json())
230             .catch(this._decodeError);
231     }
232     private _post(url: string, body: any): Observable<any> {
233         return this.http.post(this.baseUrl + url, JSON.stringify(body), this._attachAuthHeaders())
234             .map((res: Response) => res.json())
235             .catch((error) => {
236                 return this._decodeError(error);
237             });
238     }
239     private _delete(url: string): Observable<any> {
240         return this.http.delete(this.baseUrl + url, this._attachAuthHeaders())
241             .map((res: Response) => res.json())
242             .catch(this._decodeError);
243     }
244
245     private _decodeError(err: any) {
246         let e: string;
247         if (typeof err === "object") {
248             if (err.statusText) {
249                 e = err.statusText;
250             } else if (err.error) {
251                 e = String(err.error);
252             } else {
253                 e = JSON.stringify(err);
254             }
255         } else {
256             e = err.json().error || 'Server error';
257         }
258         return Observable.throw(e);
259     }
260 }