b69a196e4a5c693801ca8ced6f003ca0c2043597
[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     clientSyncThingID: string;
24     type: number;
25     label?: string;
26     defaultSdkID?: string;
27 }
28
29 interface IXDSBuilderConfig {
30     ip: string;
31     port: string;
32     syncThingID: string;
33 }
34
35 export interface IXDSFolderConfig {
36     id: string;
37     label: string;
38     path: string;
39     type: number;
40     status?: string;
41     isInSync?: boolean;
42     defaultSdkID: string;
43
44     // FIXME better with union but tech pb with go code
45     //data?: IXDSPathMapConfig|IXDSCloudSyncConfig;
46     dataPathMap?: IXDSPathMapConfig;
47     dataCloudSync?: IXDSCloudSyncConfig;
48 }
49
50 export interface IXDSPathMapConfig {
51     // TODO
52     serverPath: string;
53 }
54
55 export interface IXDSCloudSyncConfig {
56     syncThingID: string;
57     builderSThgID?: string;
58 }
59
60 interface IXDSConfig {
61     version: number;
62     builder: IXDSBuilderConfig;
63     folders: IXDSFolderConfig[];
64 }
65
66 export interface IXDSAgentTarball {
67     os: string;
68     arch: string;
69     version: string;
70     rawVersion: string;
71     fileUrl: string;
72 }
73
74 export interface IXDSAgentInfo {
75     tarballs: IXDSAgentTarball[];
76 }
77
78 export interface ISdkMessage {
79     wsID: string;
80     msgType: string;
81     data: any;
82 }
83
84 export interface ICmdOutput {
85     cmdID: string;
86     timestamp: string;
87     stdout: string;
88     stderr: string;
89 }
90
91 export interface ICmdExit {
92     cmdID: string;
93     timestamp: string;
94     code: number;
95     error: string;
96 }
97
98 export interface IServerStatus {
99     WS_connected: boolean;
100
101 }
102
103 const FOLDER_TYPE_CLOUDSYNC = 2;
104
105 @Injectable()
106 export class XDSServerService {
107
108     public CmdOutput$ = <Subject<ICmdOutput>>new Subject();
109     public CmdExit$ = <Subject<ICmdExit>>new Subject();
110     public FolderStateChange$ = <Subject<IXDSFolderConfig>>new Subject();
111     public Status$: Observable<IServerStatus>;
112
113
114     private baseUrl: string;
115     private wsUrl: string;
116     private _status = { WS_connected: false };
117     private statusSubject = <BehaviorSubject<IServerStatus>>new BehaviorSubject(this._status);
118
119
120     private socket: SocketIOClient.Socket;
121
122     constructor(private http: Http, private _window: Window, private alert: AlertService) {
123
124         this.Status$ = this.statusSubject.asObservable();
125
126         this.baseUrl = this._window.location.origin + '/api/v1';
127         let re = this._window.location.origin.match(/http[s]?:\/\/([^\/]*)[\/]?/);
128         if (re === null || re.length < 2) {
129             console.error('ERROR: cannot determine Websocket url');
130         } else {
131             this.wsUrl = 'ws://' + re[1];
132             this._handleIoSocket();
133             this._RegisterEvents();
134         }
135     }
136
137     private _WSState(sts: boolean) {
138         this._status.WS_connected = sts;
139         this.statusSubject.next(Object.assign({}, this._status));
140     }
141
142     private _handleIoSocket() {
143         this.socket = io(this.wsUrl, { transports: ['websocket'] });
144
145         this.socket.on('connect_error', (res) => {
146             this._WSState(false);
147             console.error('WS Connect_error ', res);
148         });
149
150         this.socket.on('connect', (res) => {
151             this._WSState(true);
152         });
153
154         this.socket.on('disconnection', (res) => {
155             this._WSState(false);
156             this.alert.error('WS disconnection: ' + res);
157         });
158
159         this.socket.on('error', (err) => {
160             console.error('WS error:', err);
161         });
162
163         this.socket.on('make:output', data => {
164             this.CmdOutput$.next(Object.assign({}, <ICmdOutput>data));
165         });
166
167         this.socket.on('make:exit', data => {
168             this.CmdExit$.next(Object.assign({}, <ICmdExit>data));
169         });
170
171         this.socket.on('exec:output', data => {
172             this.CmdOutput$.next(Object.assign({}, <ICmdOutput>data));
173         });
174
175         this.socket.on('exec:exit', data => {
176             this.CmdExit$.next(Object.assign({}, <ICmdExit>data));
177         });
178
179         this.socket.on('event:FolderStateChanged', ev => {
180             if (ev && ev.folder) {
181                 this.FolderStateChange$.next(Object.assign({}, ev.folder));
182             }
183         });
184     }
185
186     private _RegisterEvents() {
187         let ev = "FolderStateChanged";
188         this._post('/events/register', { "name": ev })
189             .subscribe(
190             res => { },
191             error => {
192                 this.alert.error("ERROR while registering events " + ev + ": ", error);
193             }
194             );
195     }
196
197     getSdks(): Observable<ISdk[]> {
198         return this._get('/sdks');
199     }
200
201     getXdsAgentInfo(): Observable<IXDSAgentInfo> {
202         return this._get('/xdsagent/info');
203     }
204
205     getProjects(): Observable<IXDSFolderConfig[]> {
206         return this._get('/folders');
207     }
208
209     addProject(cfg: IXDSFolderConfig): Observable<IXDSFolderConfig> {
210         return this._post('/folder', cfg);
211     }
212
213     deleteProject(id: string): Observable<IXDSFolderConfig> {
214         return this._delete('/folder/' + id);
215     }
216
217     syncProject(id: string): Observable<string> {
218         return this._post('/folder/sync/' + id, {});
219     }
220
221     exec(prjID: string, dir: string, cmd: string, sdkid?: string, args?: string[], env?: string[]): Observable<any> {
222         return this._post('/exec',
223             {
224                 id: prjID,
225                 rpath: dir,
226                 cmd: cmd,
227                 sdkid: sdkid || "",
228                 args: args || [],
229                 env: env || [],
230             });
231     }
232
233     make(prjID: string, dir: string, sdkid?: string, args?: string[], env?: string[]): Observable<any> {
234         return this._post('/make',
235             {
236                 id: prjID,
237                 rpath: dir,
238                 sdkid: sdkid,
239                 args: args || [],
240                 env: env || [],
241             });
242     }
243
244
245     private _attachAuthHeaders(options?: any) {
246         options = options || {};
247         let headers = options.headers || new Headers();
248         // headers.append('Authorization', 'Basic ' + btoa('username:password'));
249         headers.append('Accept', 'application/json');
250         headers.append('Content-Type', 'application/json');
251         // headers.append('Access-Control-Allow-Origin', '*');
252
253         options.headers = headers;
254         return options;
255     }
256
257     private _get(url: string): Observable<any> {
258         return this.http.get(this.baseUrl + url, this._attachAuthHeaders())
259             .map((res: Response) => res.json())
260             .catch(this._decodeError);
261     }
262     private _post(url: string, body: any): Observable<any> {
263         return this.http.post(this.baseUrl + url, JSON.stringify(body), this._attachAuthHeaders())
264             .map((res: Response) => res.json())
265             .catch((error) => {
266                 return this._decodeError(error);
267             });
268     }
269     private _delete(url: string): Observable<any> {
270         return this.http.delete(this.baseUrl + url, this._attachAuthHeaders())
271             .map((res: Response) => res.json())
272             .catch(this._decodeError);
273     }
274
275     private _decodeError(err: any) {
276         let e: string;
277         if (err instanceof Response) {
278             const body = err.json() || 'Server error';
279             e = body.error || JSON.stringify(body);
280             if (!e || e === "") {
281                 e = `${err.status} - ${err.statusText || 'Unknown error'}`;
282             }
283         } else if (typeof err === "object") {
284             if (err.statusText) {
285                 e = err.statusText;
286             } else if (err.error) {
287                 e = String(err.error);
288             } else {
289                 e = JSON.stringify(err);
290             }
291         } else {
292             e = err.message ? err.message : err.toString();
293         }
294         return Observable.throw(e);
295     }
296 }