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