Used non default syncthing port to avoid confict.
[src/xds/xds-server.git] / webapp / src / app / services / config.service.ts
index 9b9f5db..3f07a6a 100644 (file)
@@ -13,28 +13,40 @@ import 'rxjs/add/observable/throw';
 import 'rxjs/add/operator/mergeMap';
 
 
-import { XDSServerService, IXDSConfigProject } from "../services/xdsserver.service";
+import { XDSServerService, IXDSFolderConfig } from "../services/xdsserver.service";
 import { XDSAgentService } from "../services/xdsagent.service";
 import { SyncthingService, ISyncThingProject, ISyncThingStatus } from "../services/syncthing.service";
 import { AlertService, IAlert } from "../services/alert.service";
 import { UtilsService } from "../services/utils.service";
 
 export enum ProjectType {
-    NATIVE = 1,
+    NATIVE_PATHMAP = 1,
     SYNCTHING = 2
 }
 
-export interface INativeProject {
-    // TODO
-}
+export var ProjectTypes = [
+    { value: ProjectType.NATIVE_PATHMAP, display: "Path mapping" },
+    { value: ProjectType.SYNCTHING, display: "Cloud Sync" }
+];
+
+export var ProjectStatus = {
+    ErrorConfig: "ErrorConfig",
+    Disable: "Disable",
+    Enable: "Enable",
+    Pause: "Pause",
+    Syncing: "Syncing"
+};
 
 export interface IProject {
     id?: string;
     label: string;
-    path: string;
+    pathClient: string;
+    pathServer?: string;
     type: ProjectType;
-    remotePrjDef?: INativeProject | ISyncThingProject;
-    localPrjDef?: any;
+    status?: string;
+    isInSync?: boolean;
+    isUsable?: boolean;
+    serverPrjDef?: IXDSFolderConfig;
     isExpanded?: boolean;
     visible?: boolean;
     defaultSdkID?: string;
@@ -52,10 +64,17 @@ export interface ILocalSTConfig {
     tilde: string;
 }
 
+export interface IxdsAgentPackage {
+    os: string;
+    arch: string;
+    version: string;
+    url: string;
+}
+
 export interface IConfig {
     xdsServerURL: string;
     xdsAgent: IXDSAgentConfig;
-    xdsAgentZipUrl: string;
+    xdsAgentPackages: IxdsAgentPackage[];
     projectsRootDir: string;
     projects: IProject[];
     localSThg: ILocalSTConfig;
@@ -70,7 +89,6 @@ export class ConfigService {
     private confStore: IConfig;
     private AgentConnectObs = null;
     private stConnectObs = null;
-    private xdsAgentZipUrl = "";
 
     constructor(private _window: Window,
         private cookie: CookieService,
@@ -99,15 +117,15 @@ export class ConfigService {
             this.confStore = {
                 xdsServerURL: this._window.location.origin + '/api/v1',
                 xdsAgent: {
-                    URL: 'http://localhost:8000',
+                    URL: 'http://localhost:8010',
                     retry: 10,
                 },
-                xdsAgentZipUrl: "",
+                xdsAgentPackages: [],
                 projectsRootDir: "",
                 projects: [],
                 localSThg: {
                     ID: null,
-                    URL: "http://localhost:8384",
+                    URL: "http://localhost:8385",
                     retry: 10,    // 10 seconds
                     tilde: "",
                 }
@@ -115,12 +133,27 @@ export class ConfigService {
         }
 
         // Update XDS Agent tarball url
-        this.confStore.xdsAgentZipUrl = "";
         this.xdsServerSvr.getXdsAgentInfo().subscribe(nfo => {
-            let os = this.utils.getOSName(true);
-            let zurl = nfo.tarballs && nfo.tarballs.filter(elem => elem.os === os);
-            if (zurl && zurl.length) {
-                this.confStore.xdsAgentZipUrl = zurl[0].fileUrl;
+            this.confStore.xdsAgentPackages = [];
+            nfo.tarballs && nfo.tarballs.forEach(el =>
+                this.confStore.xdsAgentPackages.push({
+                    os: el.os,
+                    arch: el.arch,
+                    version: el.version,
+                    url: el.fileUrl
+                })
+            );
+            this.confSubject.next(Object.assign({}, this.confStore));
+        });
+
+        // Update Project data
+        this.xdsServerSvr.FolderStateChange$.subscribe(prj => {
+            let i = this._getProjectIdx(prj.id);
+            if (i >= 0) {
+                // XXX for now, only isInSync and status may change
+                this.confStore.projects[i].isInSync = prj.isInSync;
+                this.confStore.projects[i].status = prj.status;
+                this.confStore.projects[i].isUsable = this._isUsableProject(prj);
                 this.confSubject.next(Object.assign({}, this.confStore));
             }
         });
@@ -156,13 +189,19 @@ export class ConfigService {
 
             }, error => {
                 if (error.indexOf("XDS local Agent not responding") !== -1) {
-                    let msg = "<span><strong>" + error + "<br></strong>";
-                    msg += "You may need to download and execute XDS-Agent.<br>";
-                    if (this.confStore.xdsAgentZipUrl !== "") {
-                        msg += "<a class=\"fa fa-download\" href=\"" + this.confStore.xdsAgentZipUrl + "\" target=\"_blank\"></a>";
-                        msg += " Download XDS-Agent tarball.";
-                    }
-                    msg += "</span>";
+                    let url_OS_Linux = "https://en.opensuse.org/LinuxAutomotive#Installation_AGL_XDS";
+                    let url_OS_Other = "https://github.com/iotbzh/xds-agent#how-to-install-on-other-platform";
+                    let msg = `<span><strong>` + error + `<br></strong>
+                    You may need to install and execute XDS-Agent: <br>
+                        On Linux machine <a href="` + url_OS_Linux + `" target="_blank"><span
+                            class="fa fa-external-link"></span></a>
+                        <br>
+                        On Windows machine <a href="` + url_OS_Other + `" target="_blank"><span
+                            class="fa fa-external-link"></span></a>
+                        <br>
+                        On MacOS machine <a href="` + url_OS_Other + `" target="_blank"><span
+                            class="fa fa-external-link"></span></a>
+                    `;
                     this.alert.error(msg);
                 } else {
                     this.alert.error(error);
@@ -197,16 +236,8 @@ export class ConfigService {
                 this.stSvr.getProjects().subscribe(localPrj => {
                     remotePrj.forEach(rPrj => {
                         let lPrj = localPrj.filter(item => item.id === rPrj.id);
-                        if (lPrj.length > 0) {
-                            let pp: IProject = {
-                                id: rPrj.id,
-                                label: rPrj.label,
-                                path: rPrj.path,
-                                type: ProjectType.SYNCTHING,    // FIXME support other types
-                                remotePrjDef: Object.assign({}, rPrj),
-                                localPrjDef: Object.assign({}, lPrj[0]),
-                            };
-                            this.confStore.projects.push(pp);
+                        if (lPrj.length > 0 || rPrj.type === ProjectType.NATIVE_PATHMAP) {
+                            this._addProject(rPrj, true);
                         }
                     });
                     this.confSubject.next(Object.assign({}, this.confStore));
@@ -258,100 +289,133 @@ export class ConfigService {
         return id.slice(0, 15);
     }
 
-    addProject(prj: IProject) {
+    addProject(prj: IProject): Observable<IProject> {
         // Substitute tilde with to user home path
-        prj.path = prj.path.trim();
-        if (prj.path.charAt(0) === '~') {
-            prj.path = this.confStore.localSThg.tilde + prj.path.substring(1);
+        let pathCli = prj.pathClient.trim();
+        if (pathCli.charAt(0) === '~') {
+            pathCli = this.confStore.localSThg.tilde + pathCli.substring(1);
 
             // Must be a full path (on Linux or Windows)
-        } else if (!((prj.path.charAt(0) === '/') ||
-            (prj.path.charAt(1) === ':' && (prj.path.charAt(2) === '\\' || prj.path.charAt(2) === '/')))) {
-            prj.path = this.confStore.projectsRootDir + '/' + prj.path;
-        }
-
-        if (prj.id == null) {
-            // FIXME - must be done on server side
-            let prefix = this.getLabelRootName() || new Date().toISOString();
-            let splath = prj.path.split('/');
-            prj.id = prefix + "_" + splath[splath.length - 1];
-        }
-
-        if (this._getProjectIdx(prj.id) !== -1) {
-            this.alert.warning("Project already exist (id=" + prj.id + ")", true);
-            return;
+        } else if (!((pathCli.charAt(0) === '/') ||
+            (pathCli.charAt(1) === ':' && (pathCli.charAt(2) === '\\' || pathCli.charAt(2) === '/')))) {
+            pathCli = this.confStore.projectsRootDir + '/' + pathCli;
         }
 
-        // TODO - support others project types
-        if (prj.type !== ProjectType.SYNCTHING) {
-            this.alert.error('Project type not supported yet (type: ' + prj.type + ')');
-            return;
-        }
-
-        let sdkPrj: IXDSConfigProject = {
-            id: prj.id,
-            label: prj.label,
-            path: prj.path,
-            hostSyncThingID: this.confStore.localSThg.ID,
+        let xdsPrj: IXDSFolderConfig = {
+            id: "",
+            label: prj.label || "",
+            path: pathCli,
+            type: prj.type,
             defaultSdkID: prj.defaultSdkID,
+            dataPathMap: {
+                serverPath: prj.pathServer,
+            },
+            dataCloudSync: {
+                syncThingID: this.confStore.localSThg.ID,
+            }
         };
-
         // Send config to XDS server
         let newPrj = prj;
-        this.xdsServerSvr.addProject(sdkPrj)
-            .subscribe(resStRemotePrj => {
-                newPrj.remotePrjDef = resStRemotePrj;
-
-                // FIXME REWORK local ST config
-                //  move logic to server side tunneling-back by WS
-
-                // Now setup local config
-                let stLocPrj: ISyncThingProject = {
-                    id: sdkPrj.id,
-                    label: sdkPrj.label,
-                    path: sdkPrj.path,
-                    remoteSyncThingID: resStRemotePrj.builderSThgID
-                };
-
-                // Set local Syncthing config
-                this.stSvr.addProject(stLocPrj)
-                    .subscribe(resStLocalPrj => {
-                        newPrj.localPrjDef = resStLocalPrj;
-
-                        // FIXME: maybe reduce subject to only .project
-                        //this.confSubject.next(Object.assign({}, this.confStore).project);
-                        this.confStore.projects.push(Object.assign({}, newPrj));
-                        this.confSubject.next(Object.assign({}, this.confStore));
-                    },
-                    err => {
-                        this.alert.error("Configuration local ERROR: " + err);
-                    });
-            },
-            err => {
-                this.alert.error("Configuration remote ERROR: " + err);
+        return this.xdsServerSvr.addProject(xdsPrj)
+            .flatMap(resStRemotePrj => {
+                xdsPrj = resStRemotePrj;
+                if (xdsPrj.type === ProjectType.SYNCTHING) {
+                    // FIXME REWORK local ST config
+                    //  move logic to server side tunneling-back by WS
+                    let stData = xdsPrj.dataCloudSync;
+
+                    // Now setup local config
+                    let stLocPrj: ISyncThingProject = {
+                        id: xdsPrj.id,
+                        label: xdsPrj.label,
+                        path: xdsPrj.path,
+                        serverSyncThingID: stData.builderSThgID
+                    };
+
+                    // Set local Syncthing config
+                    return this.stSvr.addProject(stLocPrj);
+
+                } else {
+                    return Observable.of(null);
+                }
+            })
+            .map(resStLocalPrj => {
+                this._addProject(xdsPrj);
+                return newPrj;
             });
     }
 
-    deleteProject(prj: IProject) {
+    deleteProject(prj: IProject): Observable<IProject> {
         let idx = this._getProjectIdx(prj.id);
+        let delPrj = prj;
         if (idx === -1) {
             throw new Error("Invalid project id (id=" + prj.id + ")");
         }
-        this.xdsServerSvr.deleteProject(prj.id)
-            .subscribe(res => {
-                this.stSvr.deleteProject(prj.id)
-                    .subscribe(res => {
-                        this.confStore.projects.splice(idx, 1);
-                    }, err => {
-                        this.alert.error("Delete local ERROR: " + err);
-                    });
-            }, err => {
-                this.alert.error("Delete remote ERROR: " + err);
+        return this.xdsServerSvr.deleteProject(prj.id)
+            .flatMap(res => {
+                if (prj.type === ProjectType.SYNCTHING) {
+                    return this.stSvr.deleteProject(prj.id);
+                }
+                return Observable.of(null);
+            })
+            .map(res => {
+                this.confStore.projects.splice(idx, 1);
+                return delPrj;
             });
     }
 
+    syncProject(prj: IProject): Observable<string> {
+        let idx = this._getProjectIdx(prj.id);
+        if (idx === -1) {
+            throw new Error("Invalid project id (id=" + prj.id + ")");
+        }
+        return this.xdsServerSvr.syncProject(prj.id);
+    }
+
+    private _isUsableProject(p) {
+        return p && p.isInSync &&
+            (p.status === ProjectStatus.Enable) &&
+            (p.status !== ProjectStatus.Syncing);
+    }
+
     private _getProjectIdx(id: string): number {
         return this.confStore.projects.findIndex((item) => item.id === id);
     }
 
-}
\ No newline at end of file
+    private _addProject(rPrj: IXDSFolderConfig, noNext?: boolean) {
+
+        // Convert XDSFolderConfig to IProject
+        let pp: IProject = {
+            id: rPrj.id,
+            label: rPrj.label,
+            pathClient: rPrj.path,
+            pathServer: rPrj.dataPathMap.serverPath,
+            type: rPrj.type,
+            status: rPrj.status,
+            isInSync: rPrj.isInSync,
+            isUsable: this._isUsableProject(rPrj),
+            defaultSdkID: rPrj.defaultSdkID,
+            serverPrjDef: Object.assign({}, rPrj),  // do a copy
+        };
+
+        // add new project
+        this.confStore.projects.push(pp);
+
+        // sort project array
+        this.confStore.projects.sort((a, b) => {
+            if (a.label < b.label) {
+                return -1;
+            }
+            if (a.label > b.label) {
+                return 1;
+            }
+            return 0;
+        });
+
+        // FIXME: maybe reduce subject to only .project
+        //this.confSubject.next(Object.assign({}, this.confStore).project);
+        if (!noNext) {
+            this.confSubject.next(Object.assign({}, this.confStore));
+        }
+    }
+}