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