c65332fc2467a0592a1570d427ba83a8733fb169
[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 interface INativeProject {
33     // TODO
34 }
35
36 export interface IProject {
37     id?: string;
38     label: string;
39     pathClient: string;
40     pathServer?: string;
41     type: ProjectType;
42     remotePrjDef?: INativeProject | ISyncThingProject;
43     localPrjDef?: any;
44     isExpanded?: boolean;
45     visible?: boolean;
46     defaultSdkID?: string;
47 }
48
49 export interface IXDSAgentConfig {
50     URL: string;
51     retry: number;
52 }
53
54 export interface ILocalSTConfig {
55     ID: string;
56     URL: string;
57     retry: number;
58     tilde: string;
59 }
60
61 export interface IxdsAgentPackage {
62     os: string;
63     arch: string;
64     version: string;
65     url: string;
66 }
67
68 export interface IConfig {
69     xdsServerURL: string;
70     xdsAgent: IXDSAgentConfig;
71     xdsAgentPackages: IxdsAgentPackage[];
72     projectsRootDir: string;
73     projects: IProject[];
74     localSThg: ILocalSTConfig;
75 }
76
77 @Injectable()
78 export class ConfigService {
79
80     public conf: Observable<IConfig>;
81
82     private confSubject: BehaviorSubject<IConfig>;
83     private confStore: IConfig;
84     private AgentConnectObs = null;
85     private stConnectObs = null;
86
87     constructor(private _window: Window,
88         private cookie: CookieService,
89         private xdsServerSvr: XDSServerService,
90         private xdsAgentSvr: XDSAgentService,
91         private stSvr: SyncthingService,
92         private alert: AlertService,
93         private utils: UtilsService,
94     ) {
95         this.load();
96         this.confSubject = <BehaviorSubject<IConfig>>new BehaviorSubject(this.confStore);
97         this.conf = this.confSubject.asObservable();
98
99         // force to load projects
100         this.loadProjects();
101     }
102
103     // Load config
104     load() {
105         // Try to retrieve previous config from cookie
106         let cookConf = this.cookie.getObject("xds-config");
107         if (cookConf != null) {
108             this.confStore = <IConfig>cookConf;
109         } else {
110             // Set default config
111             this.confStore = {
112                 xdsServerURL: this._window.location.origin + '/api/v1',
113                 xdsAgent: {
114                     URL: 'http://localhost:8000',
115                     retry: 10,
116                 },
117                 xdsAgentPackages: [],
118                 projectsRootDir: "",
119                 projects: [],
120                 localSThg: {
121                     ID: null,
122                     URL: "http://localhost:8384",
123                     retry: 10,    // 10 seconds
124                     tilde: "",
125                 }
126             };
127         }
128
129         // Update XDS Agent tarball url
130         this.xdsServerSvr.getXdsAgentInfo().subscribe(nfo => {
131             this.confStore.xdsAgentPackages = [];
132             nfo.tarballs && nfo.tarballs.forEach(el =>
133                 this.confStore.xdsAgentPackages.push({
134                     os: el.os,
135                     arch: el.arch,
136                     version: el.version,
137                     url: el.fileUrl
138                 })
139             );
140             this.confSubject.next(Object.assign({}, this.confStore));
141         });
142     }
143
144     // Save config into cookie
145     save() {
146         // Notify subscribers
147         this.confSubject.next(Object.assign({}, this.confStore));
148
149         // Don't save projects in cookies (too big!)
150         let cfg = Object.assign({}, this.confStore);
151         delete (cfg.projects);
152         this.cookie.putObject("xds-config", cfg);
153     }
154
155     loadProjects() {
156         // Setup connection with local XDS agent
157         if (this.AgentConnectObs) {
158             try {
159                 this.AgentConnectObs.unsubscribe();
160             } catch (err) { }
161             this.AgentConnectObs = null;
162         }
163
164         let cfg = this.confStore.xdsAgent;
165         this.AgentConnectObs = this.xdsAgentSvr.connect(cfg.retry, cfg.URL)
166             .subscribe((sts) => {
167                 //console.log("Agent sts", sts);
168                 // FIXME: load projects from local XDS Agent and
169                 //  not directly from local syncthing
170                 this._loadProjectFromLocalST();
171
172             }, error => {
173                 if (error.indexOf("XDS local Agent not responding") !== -1) {
174                     let msg = "<span><strong>" + error + "<br></strong>";
175                     msg += "You may need to download and execute XDS-Agent.<br>";
176
177                     let os = this.utils.getOSName(true);
178                     let zurl = this.confStore.xdsAgentPackages && this.confStore.xdsAgentPackages.filter(elem => elem.os === os);
179                     if (zurl && zurl.length) {
180                         msg += " Download XDS-Agent tarball for " + zurl[0].os + " host OS ";
181                         msg += "<a class=\"fa fa-download\" href=\"" + zurl[0].url + "\" target=\"_blank\"></a>";
182                     }
183                     msg += "</span>";
184                     this.alert.error(msg);
185                 } else {
186                     this.alert.error(error);
187                 }
188             });
189     }
190
191     private _loadProjectFromLocalST() {
192         // Remove previous subscriber if existing
193         if (this.stConnectObs) {
194             try {
195                 this.stConnectObs.unsubscribe();
196             } catch (err) { }
197             this.stConnectObs = null;
198         }
199
200         // FIXME: move this code and all logic about syncthing inside XDS Agent
201         // Setup connection with local SyncThing
202         let retry = this.confStore.localSThg.retry;
203         let url = this.confStore.localSThg.URL;
204         this.stConnectObs = this.stSvr.connect(retry, url).subscribe((sts) => {
205             this.confStore.localSThg.ID = sts.ID;
206             this.confStore.localSThg.tilde = sts.tilde;
207             if (this.confStore.projectsRootDir === "") {
208                 this.confStore.projectsRootDir = sts.tilde;
209             }
210
211             // Rebuild projects definition from local and remote syncthing
212             this.confStore.projects = [];
213
214             this.xdsServerSvr.getProjects().subscribe(remotePrj => {
215                 this.stSvr.getProjects().subscribe(localPrj => {
216                     remotePrj.forEach(rPrj => {
217                         let lPrj = localPrj.filter(item => item.id === rPrj.id);
218                         if (lPrj.length > 0) {
219                             let pp: IProject = {
220                                 id: rPrj.id,
221                                 label: rPrj.label,
222                                 pathClient: rPrj.path,
223                                 pathServer: rPrj.dataPathMap.serverPath,
224                                 type: rPrj.type,
225                                 remotePrjDef: Object.assign({}, rPrj),
226                                 localPrjDef: Object.assign({}, lPrj[0]),
227                             };
228                             this.confStore.projects.push(pp);
229                         }
230                     });
231                     this.confSubject.next(Object.assign({}, this.confStore));
232                 }), error => this.alert.error('Could not load initial state of local projects.');
233             }), error => this.alert.error('Could not load initial state of remote projects.');
234
235         }, error => {
236             if (error.indexOf("Syncthing local daemon not responding") !== -1) {
237                 let msg = "<span><strong>" + error + "<br></strong>";
238                 msg += "Please check that local XDS-Agent is running.<br>";
239                 msg += "</span>";
240                 this.alert.error(msg);
241             } else {
242                 this.alert.error(error);
243             }
244         });
245     }
246
247     set syncToolURL(url: string) {
248         this.confStore.localSThg.URL = url;
249         this.save();
250     }
251
252     set xdsAgentRetry(r: number) {
253         this.confStore.localSThg.retry = r;
254         this.confStore.xdsAgent.retry = r;
255         this.save();
256     }
257
258     set xdsAgentUrl(url: string) {
259         this.confStore.xdsAgent.URL = url;
260         this.save();
261     }
262
263
264     set projectsRootDir(p: string) {
265         if (p.charAt(0) === '~') {
266             p = this.confStore.localSThg.tilde + p.substring(1);
267         }
268         this.confStore.projectsRootDir = p;
269         this.save();
270     }
271
272     getLabelRootName(): string {
273         let id = this.confStore.localSThg.ID;
274         if (!id || id === "") {
275             return null;
276         }
277         return id.slice(0, 15);
278     }
279
280     addProject(prj: IProject) {
281         // Substitute tilde with to user home path
282         let pathCli = prj.pathClient.trim();
283         if (pathCli.charAt(0) === '~') {
284             pathCli = this.confStore.localSThg.tilde + pathCli.substring(1);
285
286             // Must be a full path (on Linux or Windows)
287         } else if (!((pathCli.charAt(0) === '/') ||
288             (pathCli.charAt(1) === ':' && (pathCli.charAt(2) === '\\' || pathCli.charAt(2) === '/')))) {
289             pathCli = this.confStore.projectsRootDir + '/' + pathCli;
290         }
291
292         let xdsPrj: IXDSFolderConfig = {
293             id: "",
294             label: prj.label || "",
295             path: pathCli,
296             type: prj.type,
297             defaultSdkID: prj.defaultSdkID,
298             dataPathMap: {
299                 serverPath: prj.pathServer,
300             },
301             dataCloudSync: {
302                 syncThingID: this.confStore.localSThg.ID,
303             }
304         };
305         // Send config to XDS server
306         let newPrj = prj;
307         this.xdsServerSvr.addProject(xdsPrj)
308             .subscribe(resStRemotePrj => {
309                 newPrj.remotePrjDef = resStRemotePrj;
310                 newPrj.id = resStRemotePrj.id;
311
312                 // FIXME REWORK local ST config
313                 //  move logic to server side tunneling-back by WS
314                 let stData = resStRemotePrj.dataCloudSync;
315
316                 // Now setup local config
317                 let stLocPrj: ISyncThingProject = {
318                     id: resStRemotePrj.id,
319                     label: xdsPrj.label,
320                     path: xdsPrj.path,
321                     serverSyncThingID: stData.builderSThgID
322                 };
323
324                 // Set local Syncthing config
325                 this.stSvr.addProject(stLocPrj)
326                     .subscribe(resStLocalPrj => {
327                         newPrj.localPrjDef = resStLocalPrj;
328
329                         // FIXME: maybe reduce subject to only .project
330                         //this.confSubject.next(Object.assign({}, this.confStore).project);
331                         this.confStore.projects.push(Object.assign({}, newPrj));
332                         this.confSubject.next(Object.assign({}, this.confStore));
333                     },
334                     err => {
335                         this.alert.error("Configuration local ERROR: " + err);
336                     });
337             },
338             err => {
339                 this.alert.error("Configuration remote ERROR: " + err);
340             });
341     }
342
343     deleteProject(prj: IProject) {
344         let idx = this._getProjectIdx(prj.id);
345         if (idx === -1) {
346             throw new Error("Invalid project id (id=" + prj.id + ")");
347         }
348         this.xdsServerSvr.deleteProject(prj.id)
349             .subscribe(res => {
350                 this.stSvr.deleteProject(prj.id)
351                     .subscribe(res => {
352                         this.confStore.projects.splice(idx, 1);
353                     }, err => {
354                         this.alert.error("Delete local ERROR: " + err);
355                     });
356             }, err => {
357                 this.alert.error("Delete remote ERROR: " + err);
358             });
359     }
360
361     private _getProjectIdx(id: string): number {
362         return this.confStore.projects.findIndex((item) => item.id === id);
363     }
364
365 }