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