Fix ssh command when execited with a wget pipe
[src/xds/xds-server.git] / webapp / src / app / services / config.service.ts
1 import { Injectable, OnInit } from '@angular/core';
2 import { Http, Headers, RequestOptionsArgs, Response } from '@angular/http';
3 import { Location } from '@angular/common';
4 import { CookieService } from 'ngx-cookie';
5 import { Observable } from 'rxjs/Observable';
6 import { Subscriber } from 'rxjs/Subscriber';
7 import { BehaviorSubject } from 'rxjs/BehaviorSubject';
8
9 // Import RxJs required methods
10 import 'rxjs/add/operator/map';
11 import 'rxjs/add/operator/catch';
12 import 'rxjs/add/observable/throw';
13 import 'rxjs/add/operator/mergeMap';
14
15
16 import { XDSServerService, IXDSFolderConfig } from "../services/xdsserver.service";
17 import { XDSAgentService } from "../services/xdsagent.service";
18 import { SyncthingService, ISyncThingProject, ISyncThingStatus } from "../services/syncthing.service";
19 import { AlertService, IAlert } from "../services/alert.service";
20 import { UtilsService } from "../services/utils.service";
21
22 export enum ProjectType {
23     NATIVE_PATHMAP = 1,
24     SYNCTHING = 2
25 }
26
27 export var ProjectTypes = [
28     { value: ProjectType.NATIVE_PATHMAP, display: "Path mapping" },
29     { value: ProjectType.SYNCTHING, display: "Cloud Sync" }
30 ];
31
32 export var ProjectStatus = {
33     ErrorConfig: "ErrorConfig",
34     Disable: "Disable",
35     Enable: "Enable",
36     Pause: "Pause",
37     Syncing: "Syncing"
38 };
39
40 export interface IProject {
41     id?: string;
42     label: string;
43     pathClient: string;
44     pathServer?: string;
45     type: ProjectType;
46     status?: string;
47     isInSync?: boolean;
48     isUsable?: boolean;
49     serverPrjDef?: IXDSFolderConfig;
50     isExpanded?: boolean;
51     visible?: boolean;
52     defaultSdkID?: string;
53 }
54
55 export interface IXDSAgentConfig {
56     URL: string;
57     retry: number;
58 }
59
60 export interface ILocalSTConfig {
61     ID: string;
62     URL: string;
63     retry: number;
64     tilde: string;
65 }
66
67 export interface IxdsAgentPackage {
68     os: string;
69     arch: string;
70     version: string;
71     url: string;
72 }
73
74 export interface IConfig {
75     xdsServerURL: string;
76     xdsAgent: IXDSAgentConfig;
77     xdsAgentPackages: IxdsAgentPackage[];
78     projectsRootDir: string;
79     projects: IProject[];
80     localSThg: ILocalSTConfig;
81 }
82
83 @Injectable()
84 export class ConfigService {
85
86     public conf: Observable<IConfig>;
87
88     private confSubject: BehaviorSubject<IConfig>;
89     private confStore: IConfig;
90     private AgentConnectObs = null;
91     private stConnectObs = null;
92
93     constructor(private _window: Window,
94         private cookie: CookieService,
95         private xdsServerSvr: XDSServerService,
96         private xdsAgentSvr: XDSAgentService,
97         private stSvr: SyncthingService,
98         private alert: AlertService,
99         private utils: UtilsService,
100     ) {
101         this.load();
102         this.confSubject = <BehaviorSubject<IConfig>>new BehaviorSubject(this.confStore);
103         this.conf = this.confSubject.asObservable();
104
105         // force to load projects
106         this.loadProjects();
107     }
108
109     // Load config
110     load() {
111         // Try to retrieve previous config from cookie
112         let cookConf = this.cookie.getObject("xds-config");
113         if (cookConf != null) {
114             this.confStore = <IConfig>cookConf;
115         } else {
116             // Set default config
117             this.confStore = {
118                 xdsServerURL: this._window.location.origin + '/api/v1',
119                 xdsAgent: {
120                     URL: 'http://localhost:8010',
121                     retry: 10,
122                 },
123                 xdsAgentPackages: [],
124                 projectsRootDir: "",
125                 projects: [],
126                 localSThg: {
127                     ID: null,
128                     URL: "http://localhost:8386",
129                     retry: 10,    // 10 seconds
130                     tilde: "",
131                 }
132             };
133         }
134
135         // Update XDS Agent tarball url
136         this.xdsServerSvr.getXdsAgentInfo().subscribe(nfo => {
137             this.confStore.xdsAgentPackages = [];
138             nfo.tarballs && nfo.tarballs.forEach(el =>
139                 this.confStore.xdsAgentPackages.push({
140                     os: el.os,
141                     arch: el.arch,
142                     version: el.version,
143                     url: el.fileUrl
144                 })
145             );
146             this.confSubject.next(Object.assign({}, this.confStore));
147         });
148
149         // Update Project data
150         this.xdsServerSvr.FolderStateChange$.subscribe(prj => {
151             let i = this._getProjectIdx(prj.id);
152             if (i >= 0) {
153                 // XXX for now, only isInSync and status may change
154                 this.confStore.projects[i].isInSync = prj.isInSync;
155                 this.confStore.projects[i].status = prj.status;
156                 this.confStore.projects[i].isUsable = this._isUsableProject(prj);
157                 this.confSubject.next(Object.assign({}, this.confStore));
158             }
159         });
160     }
161
162     // Save config into cookie
163     save() {
164         // Notify subscribers
165         this.confSubject.next(Object.assign({}, this.confStore));
166
167         // Don't save projects in cookies (too big!)
168         let cfg = Object.assign({}, this.confStore);
169         delete (cfg.projects);
170         this.cookie.putObject("xds-config", cfg);
171     }
172
173     loadProjects() {
174         // Setup connection with local XDS agent
175         if (this.AgentConnectObs) {
176             try {
177                 this.AgentConnectObs.unsubscribe();
178             } catch (err) { }
179             this.AgentConnectObs = null;
180         }
181
182         let cfg = this.confStore.xdsAgent;
183         this.AgentConnectObs = this.xdsAgentSvr.connect(cfg.retry, cfg.URL)
184             .subscribe((sts) => {
185                 //console.log("Agent sts", sts);
186                 // FIXME: load projects from local XDS Agent and
187                 //  not directly from local syncthing
188                 this._loadProjectFromLocalST();
189
190             }, error => {
191                 if (error.indexOf("XDS local Agent not responding") !== -1) {
192                     let rootUrl = "http://docs.automotivelinux.org/docs/devguides/en/dev/reference/";
193                     let url_OS_Linux = rootUrl + "xds/part-1/1_install-client.html#install-packages-for-debian-distro-type";
194                     let url_OS_Other = rootUrl + "xds/part-1/1_install-client.html#install-for-other-platforms-windows--macos";
195                     let msg = `<span><strong>` + error + `<br></strong>
196                     You may need to install and execute XDS-Agent: <br>
197                         On Linux machine <a href="` + url_OS_Linux + `" target="_blank"><span
198                             class="fa fa-external-link"></span></a>
199                         <br>
200                         On Windows machine <a href="` + url_OS_Other + `" target="_blank"><span
201                             class="fa fa-external-link"></span></a>
202                         <br>
203                         On MacOS machine <a href="` + url_OS_Other + `" target="_blank"><span
204                             class="fa fa-external-link"></span></a>
205                     `;
206                     this.alert.error(msg);
207                 } else {
208                     this.alert.error(error);
209                 }
210             });
211     }
212
213     private _loadProjectFromLocalST() {
214         // Remove previous subscriber if existing
215         if (this.stConnectObs) {
216             try {
217                 this.stConnectObs.unsubscribe();
218             } catch (err) { }
219             this.stConnectObs = null;
220         }
221
222         // FIXME: move this code and all logic about syncthing inside XDS Agent
223         // Setup connection with local SyncThing
224         let retry = this.confStore.localSThg.retry;
225         let url = this.confStore.localSThg.URL;
226         this.stConnectObs = this.stSvr.connect(retry, url).subscribe((sts) => {
227             this.confStore.localSThg.ID = sts.ID;
228             this.confStore.localSThg.tilde = sts.tilde;
229             if (this.confStore.projectsRootDir === "") {
230                 this.confStore.projectsRootDir = sts.tilde;
231             }
232
233             // Rebuild projects definition from local and remote syncthing
234             this.confStore.projects = [];
235
236             this.xdsServerSvr.getProjects().subscribe(remotePrj => {
237                 this.stSvr.getProjects().subscribe(localPrj => {
238                     remotePrj.forEach(rPrj => {
239                         let lPrj = localPrj.filter(item => item.id === rPrj.id);
240                         if (lPrj.length > 0 || rPrj.type === ProjectType.NATIVE_PATHMAP) {
241                             this._addProject(rPrj, true);
242                         }
243                     });
244                     this.confSubject.next(Object.assign({}, this.confStore));
245                 }), error => this.alert.error('Could not load initial state of local projects.');
246             }), error => this.alert.error('Could not load initial state of remote projects.');
247
248         }, error => {
249             if (error.indexOf("Syncthing local daemon not responding") !== -1) {
250                 let msg = "<span><strong>" + error + "<br></strong>";
251                 msg += "Please check that local XDS-Agent is running.<br>";
252                 msg += "</span>";
253                 this.alert.error(msg);
254             } else {
255                 this.alert.error(error);
256             }
257         });
258     }
259
260     set syncToolURL(url: string) {
261         this.confStore.localSThg.URL = url;
262         this.save();
263     }
264
265     set xdsAgentRetry(r: number) {
266         this.confStore.localSThg.retry = r;
267         this.confStore.xdsAgent.retry = r;
268         this.save();
269     }
270
271     set xdsAgentUrl(url: string) {
272         this.confStore.xdsAgent.URL = url;
273         this.save();
274     }
275
276
277     set projectsRootDir(p: string) {
278         if (p.charAt(0) === '~') {
279             p = this.confStore.localSThg.tilde + p.substring(1);
280         }
281         this.confStore.projectsRootDir = p;
282         this.save();
283     }
284
285     getLabelRootName(): string {
286         let id = this.confStore.localSThg.ID;
287         if (!id || id === "") {
288             return null;
289         }
290         return id.slice(0, 15);
291     }
292
293     addProject(prj: IProject): Observable<IProject> {
294         // Substitute tilde with to user home path
295         let pathCli = prj.pathClient.trim();
296         if (pathCli.charAt(0) === '~') {
297             pathCli = this.confStore.localSThg.tilde + pathCli.substring(1);
298
299             // Must be a full path (on Linux or Windows)
300         } else if (!((pathCli.charAt(0) === '/') ||
301             (pathCli.charAt(1) === ':' && (pathCli.charAt(2) === '\\' || pathCli.charAt(2) === '/')))) {
302             pathCli = this.confStore.projectsRootDir + '/' + pathCli;
303         }
304
305         let xdsPrj: IXDSFolderConfig = {
306             id: "",
307             label: prj.label || "",
308             path: pathCli,
309             type: prj.type,
310             defaultSdkID: prj.defaultSdkID,
311             dataPathMap: {
312                 serverPath: prj.pathServer,
313             },
314             dataCloudSync: {
315                 syncThingID: this.confStore.localSThg.ID,
316             }
317         };
318         // Send config to XDS server
319         let newPrj = prj;
320         return this.xdsServerSvr.addProject(xdsPrj)
321             .flatMap(resStRemotePrj => {
322                 xdsPrj = resStRemotePrj;
323                 if (xdsPrj.type === ProjectType.SYNCTHING) {
324                     // FIXME REWORK local ST config
325                     //  move logic to server side tunneling-back by WS
326                     let stData = xdsPrj.dataCloudSync;
327
328                     // Now setup local config
329                     let stLocPrj: ISyncThingProject = {
330                         id: xdsPrj.id,
331                         label: xdsPrj.label,
332                         path: xdsPrj.path,
333                         serverSyncThingID: stData.builderSThgID
334                     };
335
336                     // Set local Syncthing config
337                     return this.stSvr.addProject(stLocPrj);
338
339                 } else {
340                     return Observable.of(null);
341                 }
342             })
343             .map(resStLocalPrj => {
344                 this._addProject(xdsPrj);
345                 return newPrj;
346             });
347     }
348
349     deleteProject(prj: IProject): Observable<IProject> {
350         let idx = this._getProjectIdx(prj.id);
351         let delPrj = prj;
352         if (idx === -1) {
353             throw new Error("Invalid project id (id=" + prj.id + ")");
354         }
355         return this.xdsServerSvr.deleteProject(prj.id)
356             .flatMap(res => {
357                 if (prj.type === ProjectType.SYNCTHING) {
358                     return this.stSvr.deleteProject(prj.id);
359                 }
360                 return Observable.of(null);
361             })
362             .map(res => {
363                 this.confStore.projects.splice(idx, 1);
364                 return delPrj;
365             });
366     }
367
368     syncProject(prj: IProject): Observable<string> {
369         let idx = this._getProjectIdx(prj.id);
370         if (idx === -1) {
371             throw new Error("Invalid project id (id=" + prj.id + ")");
372         }
373         return this.xdsServerSvr.syncProject(prj.id);
374     }
375
376     private _isUsableProject(p) {
377         return p && p.isInSync &&
378             (p.status === ProjectStatus.Enable) &&
379             (p.status !== ProjectStatus.Syncing);
380     }
381
382     private _getProjectIdx(id: string): number {
383         return this.confStore.projects.findIndex((item) => item.id === id);
384     }
385
386     private _addProject(rPrj: IXDSFolderConfig, noNext?: boolean) {
387
388         // Convert XDSFolderConfig to IProject
389         let pp: IProject = {
390             id: rPrj.id,
391             label: rPrj.label,
392             pathClient: rPrj.path,
393             pathServer: rPrj.dataPathMap.serverPath,
394             type: rPrj.type,
395             status: rPrj.status,
396             isInSync: rPrj.isInSync,
397             isUsable: this._isUsableProject(rPrj),
398             defaultSdkID: rPrj.defaultSdkID,
399             serverPrjDef: Object.assign({}, rPrj),  // do a copy
400         };
401
402         // add new project
403         this.confStore.projects.push(pp);
404
405         // sort project array
406         this.confStore.projects.sort((a, b) => {
407             if (a.label < b.label) {
408                 return -1;
409             }
410             if (a.label > b.label) {
411                 return 1;
412             }
413             return 0;
414         });
415
416         // FIXME: maybe reduce subject to only .project
417         //this.confSubject.next(Object.assign({}, this.confStore).project);
418         if (!noNext) {
419             this.confSubject.next(Object.assign({}, this.confStore));
420         }
421     }
422 }