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